import re
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]


def _env_example_keys() -> set[str]:
    keys = set()
    for raw_line in (ROOT / ".env.example").read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        key, _value = line.split("=", 1)
        keys.add(key.strip())
    return keys


def _runtime_env_keys_from_code() -> set[str]:
    pattern = re.compile(r"""os\.(?:getenv|environ\.get)\(\s*['"]([A-Z][A-Z0-9_]+)['"]""")
    scan_paths = [
        ROOT / "app.py",
        ROOT / "config.py",
        ROOT / "scheduler.py",
        ROOT / "run_scheduler.py",
        ROOT / "routes",
        ROOT / "services",
        ROOT / "utils",
    ]

    keys = set()
    for scan_path in scan_paths:
        paths = scan_path.rglob("*.py") if scan_path.is_dir() else [scan_path]
        for path in paths:
            if not path.exists() or "__pycache__" in path.parts:
                continue
            content = path.read_text(encoding="utf-8", errors="ignore")
            keys.update(match.group(1) for match in pattern.finditer(content))
    return keys


def _compose_env_keys() -> set[str]:
    pattern = re.compile(r"""\$\{([A-Z][A-Z0-9_]+)(?::-[^}]*)?\}""")
    keys = set()
    for path in ROOT.glob("docker-compose*.yml"):
        keys.update(pattern.findall(path.read_text(encoding="utf-8", errors="ignore")))
    return keys


def test_phase3f_orphan_ai_services_stay_removed():
    orphan_services = [
        "services/elephant_alpha_decision_router.py",
        "services/telegram_ai_integration.py",
        "services/watcher_agent.py",
    ]

    assert [path for path in orphan_services if (ROOT / path).exists()] == []


def test_app_py_stays_blueprint_only_for_routes():
    app_source = (ROOT / "app.py").read_text(encoding="utf-8")

    assert "@app.route" not in app_source


def test_app_py_does_not_start_with_stale_restart_todo_banner():
    app_source = (ROOT / "app.py").read_text(encoding="utf-8")

    assert not app_source.startswith("# ================= TODO LIST")
    assert "重開機後請依序執行" not in app_source


def test_active_guides_do_not_point_to_removed_ai_services():
    active_guides = [
        ROOT / "docs" / "ELEPHANT_ALPHA_SETUP.md",
    ]
    removed_modules = [
        "services/telegram_ai_integration.py",
        "services/watcher_agent.py",
    ]

    for guide in active_guides:
        content = guide.read_text(encoding="utf-8")
        assert "Modify routing behavior in `services/elephant_alpha_decision_router.py`" not in content
        for module_path in removed_modules:
            assert module_path not in content


def test_env_example_documents_runtime_and_ai_automation_variables():
    expected_keys = {
        "AI_CALL_LOGGING_ENABLED",
        "AUTO_FIX_ENABLED",
        "AWAITING_REVIEW_PUSH_BATCH",
        "CODE_REVIEW_AUTO_FIX_ENABLED",
        "CODE_REVIEW_OLLAMA_MODEL",
        "CODE_REVIEW_OLLAMA_TIMEOUT",
        "COST_THROTTLE_ENABLED",
        "COST_THROTTLE_PROJECT_RATIO",
        "COST_UNTHROTTLE_PROJECT_RATIO",
        "DEEPSEEK_API_KEY",
        "DEEPSEEK_BASE_URL",
        "DEEPSEEK_DIRECT_ENABLED",
        "DEEPSEEK_MODEL",
        "DEEPSEEK_TIMEOUT",
        "ELEPHANT_ALPHA_ALLOWED_SSH_HOSTS",
        "ELEPHANT_ALPHA_URL",
        "EMBEDDING_HOST",
        "EMBEDDING_TIMEOUT",
        "GUNICORN_TIMEOUT",
        "GUNICORN_THREADS",
        "GUNICORN_WORKER_CLASS",
        "LINE_ENABLED",
        "MOMO_AI_AUTOMATION_SMOKE_HISTORY",
        "MOMO_AI_AUTOMATION_SMOKE_HISTORY_LIMIT",
        "MOMO_ALLOW_INSECURE_INTERNAL_WEBHOOK_FOR_DEV",
        "MOMO_EVENT_ROUTER_DEFAULT_DEDUP_SEC",
        "MOMO_EVENT_ROUTER_QUEUE",
        "MOMO_EVENT_ROUTER_REPLAY_LIMIT",
        "MOMO_EVENT_ROUTER_REPLAY_ON_SUCCESS",
        "MCP_FILESYSTEM_URL",
        "MCP_FIRECRAWL_URL",
        "MCP_CACHE_TTL_SEC",
        "MCP_MAX_RESULT_BYTES",
        "MCP_OMNISEARCH_URL",
        "MCP_POSTGRES_URL",
        "MCP_ROUTER_ENABLED",
        "MCP_TIMEOUT_SEC",
        "MODEL_ROUTER_ENABLED",
        "N8N_HOST",
        "N8N_PASSWORD",
        "N8N_PROTOCOL",
        "N8N_USER",
        "N8N_WEBHOOK_BASE_URL",
        "NEMOTRON_OLLAMA_FIRST",
        "NEMOTRON_OLLAMA_MODEL",
        "NEMOTRON_OLLAMA_TIMEOUT",
        "OLLAMA_EMBED_TIMEOUT",
        "OPENCLAW_ADMIN_USER_IDS",
        "OPENCLAW_AGENT_DISPATCH",
        "OPENCLAW_AGENT_DISPATCH_CMDS",
        "OPENCLAW_ALLOW_PRIVATE_WITHOUT_WHITELIST",
        "OPENCLAW_DAILY_HERMES_TEMPLATE",
        "OPENCLAW_OLLAMA_MODEL",
        "OPENCLAW_PPT_CACHE_TTL_HOURS",
        "OPENCLAW_IMAGE_GEMINI_MODEL",
        "OPENCLAW_IMAGE_OLLAMA_TIMEOUT",
        "OPENCLAW_IMAGE_VISION_MODEL",
        "OPENCLAW_QA_OLLAMA_FIRST",
        "OPENCLAW_QA_OLLAMA_MODEL",
        "OPENCLAW_QA_OLLAMA_TIMEOUT",
        "PPT_VISION_ENABLED",
        "PPT_VISION_MODEL",
        "PPT_VISION_TIMEOUT",
        "PROMOTION_PENDING_BATCH_SIZE",
        "RAG_ENABLED",
        "RAG_DEFAULT_THRESHOLD",
        "RAG_DEFAULT_TOP_K",
        "RAG_EMBED_DIM",
        "RAG_EMBED_MODEL",
        "RAG_EMBED_NORMALIZE",
        "TELEGRAM_ADMIN_CHAT_ID",
        "TELEGRAM_BOT_USERNAME",
        "TELEGRAM_CHAT_ID",
        "WEB_CONCURRENCY",
    }

    assert expected_keys <= _env_example_keys()


def test_env_example_documents_runtime_os_env_keys():
    internal_runtime_keys = {
        "MOMO_ALLOW_INSECURE_CONFIG_FOR_TESTS",
        "PYTEST_CURRENT_TEST",
    }

    assert _runtime_env_keys_from_code() - internal_runtime_keys <= _env_example_keys()


def test_env_example_documents_docker_compose_variables():
    assert _compose_env_keys() <= _env_example_keys()


def test_scheduler_does_not_silently_swallow_exceptions():
    scheduler_source = (ROOT / "scheduler.py").read_text(encoding="utf-8")

    assert "except:" not in scheduler_source
    assert not re.search(r"except(?: Exception)?[^\n]*:\n\s+pass(?:\s|#|$)", scheduler_source)


def test_v2_flagged_silent_exception_sites_stay_logged():
    openclaw_source = (ROOT / "routes" / "openclaw_bot_routes.py").read_text(encoding="utf-8")
    notification_source = (ROOT / "services" / "notification_manager.py").read_text(encoding="utf-8")

    assert "trend chart temp cleanup failed" in openclaw_source
    assert "trend agg chart temp cleanup failed" in openclaw_source
    assert "[Notification] 讀取 public_url 設定失敗" in notification_source


def test_v2_flagged_imports_are_removed_or_active():
    app_source = (ROOT / "app.py").read_text(encoding="utf-8")
    scheduler_source = (ROOT / "scheduler.py").read_text(encoding="utf-8")

    for module_name in ["math", "hashlib", "zipfile", "io", "traceback"]:
        assert not re.search(rf"^\s*import\s+{module_name}\b", app_source, re.MULTILINE)
    assert "EXCEL_EXPORT_DIR" not in app_source
    assert "DATABASE_TYPE" not in app_source

    assert re.search(r"^\s*import\s+schedule\b", app_source, re.MULTILINE)
    assert "schedule.run_pending()" in app_source
    assert "schedule.every(" in app_source
    assert not re.search(r"^\s*import\s+schedule\b", scheduler_source, re.MULTILINE)


def test_tracked_backup_artifacts_stay_removed():
    forbidden_artifacts = [
        "app.py.backup_login_required",
        "vendor_stockout_list.html.backup",
        "database/edm_dashboard.html",
        "templates/list.html",
        "tests/main_test.py",
    ]

    assert [path for path in forbidden_artifacts if (ROOT / path).exists()] == []


def test_active_code_no_longer_references_legacy_5888_port():
    active_paths = [
        ROOT / "app.py",
        ROOT / "tests",
        ROOT / "AUTO_IMPORT_README.md",
        ROOT / "GOOGLE_DRIVE_SETUP.md",
        ROOT / "start_momo.command",
        ROOT / "scripts" / "archive" / "check_email_status.py",
    ]

    offenders = []
    for active_path in active_paths:
        paths = active_path.rglob("*") if active_path.is_dir() else [active_path]
        for path in paths:
            if (
                not path.is_file()
                or path == Path(__file__).resolve()
                or "__pycache__" in path.parts
                or path.suffix == ".pyc"
            ):
                continue
            content = path.read_text(encoding="utf-8", errors="ignore")
            if "5888" in content:
                offenders.append(str(path.relative_to(ROOT)))

    assert offenders == []


def test_executable_scripts_do_not_use_remove_orphans():
    script_paths = [
        ROOT / "scripts",
        ROOT / "docker",
        ROOT / ".gitea" / "workflows",
    ]

    offenders = []
    for script_root in script_paths:
        if not script_root.exists():
            continue
        for path in script_root.rglob("*"):
            if not path.is_file():
                continue
            content = path.read_text(encoding="utf-8", errors="ignore")
            if "--remove-orphans" in content or "docker compose down" in content:
                offenders.append(str(path.relative_to(ROOT)))

    assert offenders == []


def test_gitea_cd_uses_ewoooc_dedicated_runner_label():
    workflow = (ROOT / ".gitea" / "workflows" / "cd.yaml").read_text(encoding="utf-8")

    assert "runs-on: ewoooc-host" in workflow
    assert "runs-on: ubuntu-latest" not in workflow
    assert "runs-on: ubuntu-22.04" not in workflow
    assert "runs-on: ubuntu-24.04" not in workflow


def test_devops_handbook_uses_current_docker_runtime_commands():
    handbook = (ROOT / "docs" / "guides" / "devops_handbook.md").read_text(encoding="utf-8")

    assert "kubectl" not in handbook
    assert "docker restart momo-pro-system" not in handbook
    assert "docker compose build --no-cache momo-pro-system" not in handbook
    assert "docker kill -s HUP momo-pro-system" in handbook
    assert "docker compose build --no-cache momo-app" in handbook
    assert "docker compose up -d --no-deps --force-recreate momo-app" in handbook
    assert "--remove-orphans" in handbook
    assert "禁止" in handbook


def test_deployment_docs_cover_jump_host_known_hosts_repair():
    sop = (ROOT / "docs" / "guides" / "deployment_sop.md").read_text(encoding="utf-8")
    handbook = (ROOT / "docs" / "guides" / "devops_handbook.md").read_text(encoding="utf-8")

    for content in (sop, handbook):
        assert "Host key verification failed" in content
        assert "ssh-keygen -R 192.168.0.188" in content
        assert "ssh-keyscan -H 192.168.0.188" in content
        assert "StrictHostKeyChecking=no" in content
        assert "不要" in content
    assert "momo_scp_smoke.txt" in sop
