from __future__ import annotations

import argparse
import json
import os
import re
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any


PROJECT_ROOT = Path(__file__).resolve().parents[1]
TESTS_DIR = PROJECT_ROOT / "e2e" / "tests"
RUNTIME_ACCOUNTS = PROJECT_ROOT / "runtime" / "e2e_accounts.json"
BUGS_DIR = PROJECT_ROOT / "bugs"
ALLURE_RESULTS = PROJECT_ROOT / "reports" / "allure-results"
ALLURE_REPORT = PROJECT_ROOT / "reports" / "allure-report"


@dataclass(frozen=True)
class TestCase:
    path: str
    markers: list[str]
    title: str


def _read_text(path: Path) -> str:
    try:
        return path.read_text(encoding="utf-8")
    except UnicodeDecodeError:
        return path.read_text(encoding="gbk", errors="replace")


def _rel(path: Path) -> str:
    return str(path.relative_to(PROJECT_ROOT)).replace("\\", "/")


def list_tests(marker: str | None = None) -> list[dict[str, Any]]:
    tests: list[dict[str, Any]] = []
    if not TESTS_DIR.exists():
        return tests

    for path in sorted(TESTS_DIR.rglob("test_*.py")):
        text = _read_text(path)
        markers = sorted(set(re.findall(r"@pytest\.mark\.([A-Za-z0-9_]+)", text)))
        titles = re.findall(r"def\s+(test_[A-Za-z0-9_]+)\s*\(", text)
        item = TestCase(path=_rel(path), markers=markers, title=", ".join(titles) or path.stem)
        if marker and marker not in item.markers:
            continue
        tests.append({"path": item.path, "markers": item.markers, "title": item.title})
    return tests


def inspect_accounts() -> dict[str, Any]:
    if not RUNTIME_ACCOUNTS.exists():
        return {"path": _rel(RUNTIME_ACCOUNTS), "exists": False, "accounts": [], "summary": {}}

    data = json.loads(_read_text(RUNTIME_ACCOUNTS))
    accounts = data if isinstance(data, list) else data.get("accounts", [])
    summary: dict[str, int] = {}
    for account in accounts:
        if not isinstance(account, dict):
            continue
        for key, value in account.items():
            if key.endswith("_status") or key in {"certification_status", "bind_status"}:
                summary[f"{key}={value}"] = summary.get(f"{key}={value}", 0) + 1

    redacted_accounts = []
    sensitive_keys = {"password", "latest_auth_key", "auth_key"}
    for account in accounts:
        if not isinstance(account, dict):
            continue
        visible = {}
        for key, value in account.items():
            if key in sensitive_keys:
                visible[key] = "***" if value else ""
            elif key == "wanzhanggui_bindings" and isinstance(value, list):
                visible[key] = [
                    {nested_key: ("***" if nested_key in sensitive_keys and nested_value else nested_value) for nested_key, nested_value in item.items()}
                    for item in value
                    if isinstance(item, dict)
                ]
            elif key == "workshop_authorizations" and isinstance(value, list):
                visible[key] = [
                    {nested_key: ("***" if nested_key in sensitive_keys and nested_value else nested_value) for nested_key, nested_value in item.items()}
                    for item in value
                    if isinstance(item, dict)
                ]
            else:
                visible[key] = value
        redacted_accounts.append(visible)

    return {
        "path": _rel(RUNTIME_ACCOUNTS),
        "exists": True,
        "count": len(accounts),
        "summary": summary,
        "accounts": redacted_accounts,
    }


def latest_bugs(limit: int = 5) -> list[dict[str, Any]]:
    if not BUGS_DIR.exists():
        return []

    bugs: list[dict[str, Any]] = []
    for path in sorted(BUGS_DIR.glob("BUG-*.md"), key=lambda p: p.stat().st_mtime, reverse=True):
        text = _read_text(path)
        first_heading = next((line.strip("# ").strip() for line in text.splitlines() if line.startswith("#")), path.stem)
        bugs.append(
            {
                "path": _rel(path),
                "title": first_heading,
                "mtime": int(path.stat().st_mtime),
                "screenshot": _rel(path.with_suffix(".png")) if path.with_suffix(".png").exists() else None,
            }
        )
        if len(bugs) >= limit:
            break
    return bugs


def allure_summary() -> dict[str, Any]:
    summary_path = ALLURE_REPORT / "widgets" / "summary.json"
    if summary_path.exists():
        return {"path": _rel(summary_path), "exists": True, "summary": json.loads(_read_text(summary_path))}

    result_files = list(ALLURE_RESULTS.glob("*-result.json")) if ALLURE_RESULTS.exists() else []
    statuses: dict[str, int] = {}
    for path in result_files:
        try:
            status = json.loads(_read_text(path)).get("status", "unknown")
        except json.JSONDecodeError:
            status = "invalid"
        statuses[status] = statuses.get(status, 0) + 1
    return {"path": _rel(ALLURE_RESULTS), "exists": ALLURE_RESULTS.exists(), "result_count": len(result_files), "statuses": statuses}


def choose_targets(profile: str) -> list[str]:
    profile = profile.strip()
    profile_map = {
        "seed": ["-m", "e2e_seed"],
        "standalone": ["-m", "e2e_standalone"],
        "chain": ["-m", "e2e_chain"],
        "main": ["e2e/tests/组合大流程/test_e2e_main_001_full_closed_loop.py"],
        "main-002": ["e2e/tests/组合大流程/test_e2e_main_002_order_to_player_settlement.py"],
        "saas-001": ["-m", "e2e_saas_001"],
        "saas-003": ["-m", "e2e_saas_003"],
        "saas-004": ["-m", "e2e_saas_004"],
        "auth": ["-m", "e2e_auth_closed_loop"],
        "push": ["-m", "e2e_push_001 or e2e_push_003"],
        "order": ["-m", "e2e_order_001 or e2e_order_002"],
    }
    if profile in profile_map:
        return profile_map[profile]
    if profile.endswith(".py") or "/" in profile or "\\" in profile:
        return [profile]
    return ["-m", profile]


def run_pytest(profile: str = "standalone", extra_args: list[str] | None = None, env: dict[str, str] | None = None) -> dict[str, Any]:
    python = PROJECT_ROOT / ".venv" / "Scripts" / "python.exe"
    executable = str(python if python.exists() else Path(sys.executable))
    args = [executable, "-m", "pytest", "-s", *choose_targets(profile), f"--alluredir={_rel(ALLURE_RESULTS)}"]
    if extra_args:
        args.extend(extra_args)

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

    completed = subprocess.run(args, cwd=PROJECT_ROOT, env=run_env, text=True)
    return {"command": args, "cwd": str(PROJECT_ROOT), "exit_code": completed.returncode, "allure_results": _rel(ALLURE_RESULTS)}


def build_plan(profile: str = "standalone") -> dict[str, Any]:
    targets = choose_targets(profile)
    if targets[:1] == ["-m"]:
        matching_tests = list_tests(marker=targets[1])
    else:
        target_set = {target.replace("\\", "/") for target in targets if target.endswith(".py")}
        matching_tests = [test for test in list_tests() if test["path"] in target_set] if target_set else list_tests()
    return {
        "profile": profile,
        "pytest_args": targets,
        "matching_tests": matching_tests,
        "accounts": inspect_accounts(),
        "recent_bugs": latest_bugs(3),
        "report": allure_summary(),
    }


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="SaaS E2E automation core helpers")
    sub = parser.add_subparsers(dest="command", required=True)
    list_parser = sub.add_parser("list-tests")
    list_parser.add_argument("--marker")
    sub.add_parser("accounts")
    bugs_parser = sub.add_parser("bugs")
    bugs_parser.add_argument("--limit", type=int, default=5)
    sub.add_parser("summary")
    plan_parser = sub.add_parser("plan")
    plan_parser.add_argument("profile", nargs="?", default="standalone")
    run_parser = sub.add_parser("run")
    run_parser.add_argument("profile", nargs="?", default="standalone")
    run_parser.add_argument("pytest_args", nargs=argparse.REMAINDER)

    args = parser.parse_args(argv)
    if args.command == "list-tests":
        payload = list_tests(args.marker)
    elif args.command == "accounts":
        payload = inspect_accounts()
    elif args.command == "bugs":
        payload = latest_bugs(args.limit)
    elif args.command == "summary":
        payload = allure_summary()
    elif args.command == "plan":
        payload = build_plan(args.profile)
    elif args.command == "run":
        payload = run_pytest(args.profile, args.pytest_args)
    else:
        raise AssertionError(args.command)

    print(json.dumps(payload, ensure_ascii=False, indent=2))
    return int(payload.get("exit_code", 0)) if isinstance(payload, dict) else 0


if __name__ == "__main__":
    raise SystemExit(main())
