"""
End-to-end tests for AKShare CLI harness.

Tests the installed CLI via subprocess. Includes TestCLISubprocess with
_resolve_cli() for testing the installed command.
"""

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

import pytest


class TestCLISubprocess:
    """E2E tests that invoke the CLI as a subprocess."""

    @staticmethod
    def _resolve_cli(name: str = "akshare-cli") -> str:
        """
        Resolve the CLI executable path.

        If CLI_ANYTHING_FORCE_INSTALLED=1 is set, uses the system PATH.
        Otherwise, falls back to the venv binary.
        """
        if os.environ.get("CLI_ANYTHING_FORCE_INSTALLED") == "1":
            path = shutil.which(name)
            if path:
                return path
            raise FileNotFoundError(
                f"'{name}' not found in PATH. "
                f"Install with: pip install -e ."
            )

        # 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

        # Try system PATH
        path = shutil.which(name)
        if path:
            return path

        # Fallback: run as module
        return None

    @classmethod
    def _run_cli(cls, args: list, timeout: int = 30, env: dict = None) -> subprocess.CompletedProcess:
        """Run CLI command and return the result."""
        cli_path = cls._resolve_cli()

        run_env = os.environ.copy()
        if env:
            run_env.update(env)

        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,
            env=run_env,
        )

    # ─── Basic CLI Tests ─────────────────────────────────────────────────

    def test_help(self):
        result = self._run_cli(["--help"])
        assert result.returncode == 0
        assert "AKShare CLI" in result.stdout
        assert "Commands:" in result.stdout

    def test_version_flag(self):
        result = self._run_cli(["--version"])
        assert result.returncode == 0
        assert "0.1.0" in result.stdout

    def test_version_command(self):
        result = self._run_cli(["version"])
        assert result.returncode == 0
        assert "cli_harness_version" in result.stdout
        assert "akshare_version" in result.stdout

    # ─── Search Tests ────────────────────────────────────────────────────

    def test_search(self):
        result = self._run_cli(["search", "stock_zh_a"])
        assert result.returncode == 0
        assert "stock_zh_a_hist" in result.stdout

    def test_search_json(self):
        result = self._run_cli(["--json", "search", "stock_zh_a"])
        assert result.returncode == 0
        data = json.loads(result.stdout)
        assert data["count"] > 0
        assert "stock_zh_a_hist" in data["functions"]

    def test_search_no_results(self):
        result = self._run_cli(["search", "zzzznonexistent999"])
        assert result.returncode == 0
        assert "No functions found" in result.stdout

    # ─── List Tests ──────────────────────────────────────────────────────

    def test_list_domains(self):
        result = self._run_cli(["list"])
        assert result.returncode == 0
        assert "stock" in result.stdout
        assert "fund" in result.stdout
        assert "functions" in result.stdout

    def test_list_domain_stock(self):
        result = self._run_cli(["list", "stock"])
        assert result.returncode == 0
        assert "stock_zh_a_hist" in result.stdout

    def test_list_json(self):
        result = self._run_cli(["--json", "list"])
        assert result.returncode == 0
        data = json.loads(result.stdout)
        assert "total_functions" in data
        assert data["total_functions"] > 500

    # ─── Info Tests ──────────────────────────────────────────────────────

    def test_info(self):
        result = self._run_cli(["info", "stock_zh_a_hist"])
        assert result.returncode == 0
        assert "stock_zh_a_hist" in result.stdout
        assert "--symbol" in result.stdout
        assert "--period" in result.stdout

    def test_info_json(self):
        result = self._run_cli(["--json", "info", "stock_zh_a_hist"])
        assert result.returncode == 0
        data = json.loads(result.stdout)
        assert data["name"] == "stock_zh_a_hist"
        assert isinstance(data["params"], list)

    def test_info_not_found(self):
        result = self._run_cli(["info", "nonexistent_xyz"])
        assert result.returncode != 0

    # ─── Call Tests (with real network) ──────────────────────────────────

    @pytest.mark.network
    def test_call_with_data(self):
        """Test calling a real akshare function (requires network)."""
        result = self._run_cli(["call", "macro_china_gdp_yearly"], timeout=60)
        assert result.returncode == 0
        assert "rows" in result.stderr or len(result.stdout) > 50

    @pytest.mark.network
    def test_call_json_output(self):
        """Test JSON output mode with real data."""
        result = self._run_cli(["--json", "call", "macro_china_gdp_yearly"], timeout=60)
        assert result.returncode == 0
        data = json.loads(result.stdout)
        assert "total_rows" in data
        assert "data" in data
        assert len(data["data"]) > 0

    @pytest.mark.network
    def test_call_csv_output(self):
        """Test CSV output mode with real data."""
        result = self._run_cli(["--csv", "call", "macro_china_gdp_yearly"], timeout=60)
        assert result.returncode == 0
        lines = result.stdout.strip().split("\n")
        assert len(lines) > 1

    def test_call_unknown_function(self):
        result = self._run_cli(["call", "totally_nonexistent_xyz"])
        assert result.returncode != 0
        assert "not found" in result.stdout.lower() or "error" in result.stdout.lower() or "not found" in result.stderr.lower()

    # ─── Subcommand Tests ────────────────────────────────────────────────

    def test_stock_help(self):
        result = self._run_cli(["stock", "--help"])
        assert result.returncode == 0
        assert "hist" in result.stdout
        assert "spot" in result.stdout

    def test_fund_help(self):
        result = self._run_cli(["fund", "--help"])
        assert result.returncode == 0
        assert "etf" in result.stdout

    def test_futures_help(self):
        result = self._run_cli(["futures", "--help"])
        assert result.returncode == 0
        assert "hist" in result.stdout

    def test_macro_help(self):
        result = self._run_cli(["macro", "--help"])
        assert result.returncode == 0
        assert "gdp" in result.stdout

    def test_forex_help(self):
        result = self._run_cli(["forex", "--help"])
        assert result.returncode == 0
        assert "spot" in result.stdout
        assert "hist" in result.stdout

    # ─── Export Tests ────────────────────────────────────────────────────

    @pytest.mark.network
    def test_output_to_file(self):
        """Test saving output directly to a file."""
        with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as f:
            filepath = f.name

        try:
            result = self._run_cli(
                ["--output", filepath, "call", "macro_china_gdp_yearly"],
                timeout=60,
            )
            assert result.returncode == 0
            assert os.path.exists(filepath)
            assert os.path.getsize(filepath) > 0
        finally:
            if os.path.exists(filepath):
                os.unlink(filepath)

    # ─── Workflow Tests ──────────────────────────────────────────────────

    def test_workflow_search_then_info(self):
        """Workflow: search for functions then get info on one."""
        # Step 1: Search
        result = self._run_cli(["search", "forex"])
        assert result.returncode == 0
        assert "forex_spot_em" in result.stdout

        # Step 2: Get info
        result = self._run_cli(["info", "forex_spot_em"])
        assert result.returncode == 0
        assert "forex_spot_em" in result.stdout

    def test_workflow_json_pipeline(self):
        """Workflow: JSON output for machine consumption."""
        result = self._run_cli(["--json", "list"])
        assert result.returncode == 0
        data = json.loads(result.stdout)
        assert isinstance(data["total_functions"], int)

        result = self._run_cli(["--json", "search", "macro_china"])
        assert result.returncode == 0
        data = json.loads(result.stdout)
        assert len(data["functions"]) > 0
