"""Module entry stories ensuring `python -m` mirrors the CLI."""

from __future__ import annotations

import os
import runpy
import subprocess
import sys
from collections.abc import Callable
from pathlib import Path

import lib_cli_exit_tools
import pytest

from bmk import __init__conf__, entry
from bmk.adapters import cli as cli_mod


def _get_subprocess_env() -> dict[str, str]:
    """Build environment dict with PYTHONPATH pointing to src/.

    Subprocess calls don't inherit pytest's sys.path modifications,
    so we must explicitly set PYTHONPATH for `python -m bmk` to work.
    """
    env = os.environ.copy()
    # Project root is 2 levels up from this test file
    project_root = Path(__file__).parent.parent
    src_path = str(project_root / "src")
    existing = env.get("PYTHONPATH", "")
    env["PYTHONPATH"] = f"{src_path}{os.pathsep}{existing}" if existing else src_path
    return env


@pytest.mark.os_agnostic
def test_module_entry_executes_cli_and_shows_help(
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """python -m invocation with no args shows help and exits 0."""
    monkeypatch.setattr(sys, "argv", ["bmk"], raising=False)

    with pytest.raises(SystemExit) as exc:
        runpy.run_module("bmk.__main__", run_name="__main__")

    captured = capsys.readouterr()
    assert exc.value.code == 0
    assert "Usage:" in captured.out
    assert __init__conf__.shell_command in captured.out


@pytest.mark.os_agnostic
def test_module_entry_formats_exceptions_via_exit_helpers(
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
    strip_ansi: Callable[[str], str],
) -> None:
    """Exceptions during module entry are formatted by lib_cli_exit_tools."""
    monkeypatch.setattr(sys, "argv", ["bmk", "fail"], raising=False)
    monkeypatch.setattr(lib_cli_exit_tools.config, "traceback", False, raising=False)
    monkeypatch.setattr(lib_cli_exit_tools.config, "traceback_force_color", False, raising=False)

    with pytest.raises(SystemExit) as exc:
        runpy.run_module("bmk.__main__", run_name="__main__")

    plain_err = strip_ansi(capsys.readouterr().err)
    assert exc.value.code != 0
    assert "RuntimeError" in plain_err or "I should fail" in plain_err


@pytest.mark.os_agnostic
def test_module_entry_traceback_flag_prints_full_traceback(
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
    strip_ansi: Callable[[str], str],
) -> None:
    """--traceback via module entry prints complete traceback on error."""
    monkeypatch.setattr(sys, "argv", ["bmk", "--traceback", "fail"])
    monkeypatch.setattr(lib_cli_exit_tools.config, "traceback", False, raising=False)
    monkeypatch.setattr(lib_cli_exit_tools.config, "traceback_force_color", False, raising=False)

    with pytest.raises(SystemExit) as exc:
        runpy.run_module("bmk.__main__", run_name="__main__")

    plain_err = strip_ansi(capsys.readouterr().err)

    assert exc.value.code != 0
    assert "Traceback (most recent call last)" in plain_err
    assert "RuntimeError: I should fail" in plain_err
    assert "[TRUNCATED" not in plain_err
    assert lib_cli_exit_tools.config.traceback is False
    assert lib_cli_exit_tools.config.traceback_force_color is False


@pytest.mark.os_agnostic
def test_module_entry_cli_exports_all_registered_commands() -> None:
    """CLI facade exports all registered commands."""
    expected_commands = {
        "cli_config",
        "cli_config_deploy",
        "cli_config_generate_examples",
        "cli_fail",
        "cli_hello",
        "cli_info",
        "cli_logdemo",
        "cli_send_email",
        "cli_send_notification",
    }
    exported = {name for name in dir(cli_mod) if name.startswith("cli_")}
    assert expected_commands.issubset(exported)


@pytest.mark.os_agnostic
def test_module_entry_subprocess_help() -> None:
    """Verify `python -m bmk --help` works via subprocess.

    This tests the true CLI invocation path that end-users would experience,
    complementing the runpy-based tests that run in-process.
    """
    result = subprocess.run(
        [sys.executable, "-m", "bmk", "--help"],
        capture_output=True,
        timeout=30,
        check=False,
        env=_get_subprocess_env(),
        # Use UTF-8 with error replacement for Windows compatibility
        # (rich-click outputs Unicode that cp1252 can't decode)
        encoding="utf-8",
        errors="replace",
    )
    assert result.returncode == 0
    assert "Usage:" in result.stdout
    assert __init__conf__.shell_command in result.stdout


@pytest.mark.os_agnostic
def test_module_entry_subprocess_version() -> None:
    """Verify `python -m bmk --version` outputs version."""
    result = subprocess.run(
        [sys.executable, "-m", "bmk", "--version"],
        capture_output=True,
        timeout=30,
        check=False,
        env=_get_subprocess_env(),
        # Use UTF-8 with error replacement for Windows compatibility
        encoding="utf-8",
        errors="replace",
    )
    assert result.returncode == 0
    assert __init__conf__.version in result.stdout


@pytest.mark.os_agnostic
def test_entry_main_invokes_cli_with_help(
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
) -> None:
    """entry.main() wires production services and invokes CLI.

    This tests the console script entry point used by pip-installed commands
    (bmk, bmk).
    """
    monkeypatch.setattr(sys, "argv", ["bmk", "--help"])

    exit_code = entry.main()

    captured = capsys.readouterr()
    assert exit_code == 0
    assert "Usage:" in captured.out
    assert __init__conf__.shell_command in captured.out


@pytest.mark.os_agnostic
def test_entry_main_returns_nonzero_on_error(
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
    strip_ansi: Callable[[str], str],
) -> None:
    """entry.main() returns non-zero exit code on CLI errors."""
    monkeypatch.setattr(sys, "argv", ["bmk", "fail"])
    monkeypatch.setattr(lib_cli_exit_tools.config, "traceback", False)
    monkeypatch.setattr(lib_cli_exit_tools.config, "traceback_force_color", False)

    exit_code = entry.main()

    plain_err = strip_ansi(capsys.readouterr().err)
    assert exit_code != 0
    assert "RuntimeError" in plain_err or "I should fail" in plain_err
