"""v0.4 Phase B pin tests.

Auto-derive ``app_name`` and ``app_version`` from the launching
package's distribution metadata when the caller omits either arg
to :func:`mgf.common.bootstrap`. Two surfaces:

  1. :func:`mgf.common._identity._derive_identity_from_metadata`
     — the helper. Tested directly with stub ``__main__`` modules
     so each branch (no module / no spec / no parent / unknown
     dist / known dist) is exercised deterministically.

  2. :func:`mgf.common.bootstrap` — wires the helper into the
     lifecycle. Tested via monkeypatch of the helper so we don't
     depend on the test runner's ``__main__`` shape (pytest's
     ``__main__.__spec__.parent`` is environment-specific).
"""

from __future__ import annotations

from importlib.metadata import PackageNotFoundError
from types import SimpleNamespace
from typing import TYPE_CHECKING

import pytest

import mgf.common
from mgf.common._identity import (
    UnconfiguredWarning,
    _derive_identity_from_metadata,
)
from mgf.common._lifecycle import AppContext

if TYPE_CHECKING:
    from pathlib import Path


# ---------------------------------------------------------------------------
# Helper fixtures (re-used from test_phase_a structure)
# ---------------------------------------------------------------------------


@pytest.fixture
def _isolated_state(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:
    monkeypatch.setenv("XDG_STATE_HOME", str(tmp_path / "state"))
    monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config"))
    return tmp_path


@pytest.fixture
def _isolated_logging() -> None:
    import logging as _logging

    from mgf.common import _identity

    prior_handlers = list(_logging.root.handlers)
    prior_level = _logging.root.level
    prior_app = _identity._APP_NAME
    prior_ver = _identity._APP_VERSION

    yield

    for h in list(_logging.root.handlers):
        _logging.root.removeHandler(h)
    for h in prior_handlers:
        _logging.root.addHandler(h)
    _logging.root.setLevel(prior_level)
    _identity._APP_NAME = prior_app
    _identity._APP_VERSION = prior_ver

    from mgf.common import _lifecycle

    _lifecycle._GLOBAL_CONTEXT = None


@pytest.fixture
def _isolated_excepthook() -> None:
    import sys
    import threading

    prior_sys = sys.excepthook
    prior_thread = threading.excepthook
    yield
    sys.excepthook = prior_sys
    threading.excepthook = prior_thread


# ---------------------------------------------------------------------------
# B.1 — _derive_identity_from_metadata helper, branch by branch
# ---------------------------------------------------------------------------


class TestDeriveIdentityHelper:
    """Direct tests for the derivation helper. Each test injects a
    stub ``main_module`` to drive a single branch."""

    def test_returns_none_when_main_module_absent(self) -> None:
        """No ``__main__`` in sys.modules (degenerate). Should return
        ``(None, None)`` rather than raise."""
        # Sentinel via passing a fake ``main_module=None`` AND
        # patching sys.modules wouldn't be safe — test the explicit
        # arg path with a module-less stub.
        result = _derive_identity_from_metadata(main_module=False)
        # ``False`` is truthy-falsy: hasattr(False, "__spec__") is
        # False → spec is None → returns (None, None).
        assert result == (None, None)

    def test_returns_none_when_main_has_no_spec(self) -> None:
        """``__main__`` exists but ``__spec__`` is missing (e.g. ad-
        hoc ``python script.py`` invocation)."""
        fake_main = SimpleNamespace()  # no __spec__ attribute
        assert _derive_identity_from_metadata(main_module=fake_main) == (None, None)

    def test_returns_none_when_spec_has_no_parent(self) -> None:
        """``__spec__`` exists but ``parent`` is empty (top-level
        invocation without a package)."""
        fake_main = SimpleNamespace(__spec__=SimpleNamespace(parent=""))
        assert _derive_identity_from_metadata(main_module=fake_main) == (None, None)

    def test_returns_none_when_spec_parent_attribute_missing(self) -> None:
        """``__spec__`` exists but doesn't expose ``parent``."""
        fake_main = SimpleNamespace(__spec__=SimpleNamespace())
        assert _derive_identity_from_metadata(main_module=fake_main) == (None, None)

    def test_returns_none_for_unknown_distribution(self) -> None:
        """``parent`` resolves but no installed dist matches."""
        fake_main = SimpleNamespace(
            __spec__=SimpleNamespace(parent="this-package-does-not-exist-zxqwy"),
        )
        assert _derive_identity_from_metadata(main_module=fake_main) == (None, None)

    def test_derives_for_installed_distribution(self) -> None:
        """``parent`` resolves to a real installed dist (we use
        ``mgf-common`` itself — guaranteed installed in this test
        env). The dist name is the hyphenated PyPI name, not the
        importable module path."""
        fake_main = SimpleNamespace(
            __spec__=SimpleNamespace(parent="mgf-common"),
        )
        name, version = _derive_identity_from_metadata(main_module=fake_main)
        assert name == "mgf-common"
        # The version is mgf-common's own __version__ — pin the shape,
        # not the value (so a future bump doesn't break this test).
        assert version is not None
        assert isinstance(version, str)
        assert len(version.split(".")) >= 2  # at least "X.Y"

    def test_swallows_packagenotfounderror(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """Defensive: even if ``distribution()`` raises something
        unexpected, the helper must not propagate."""
        from importlib import metadata as _md

        def _boom(name: str) -> object:
            raise PackageNotFoundError(name)

        monkeypatch.setattr(_md, "distribution", _boom)
        fake_main = SimpleNamespace(
            __spec__=SimpleNamespace(parent="anything"),
        )
        assert _derive_identity_from_metadata(main_module=fake_main) == (None, None)

    def test_swallows_unexpected_exception(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """Even if ``distribution()`` raises an arbitrary error
        (transient I/O, broken metadata) — return (None, None)."""
        from importlib import metadata as _md

        def _boom(name: str) -> object:
            raise RuntimeError("unexpected")

        monkeypatch.setattr(_md, "distribution", _boom)
        fake_main = SimpleNamespace(
            __spec__=SimpleNamespace(parent="anything"),
        )
        assert _derive_identity_from_metadata(main_module=fake_main) == (None, None)


# ---------------------------------------------------------------------------
# B.2 — bootstrap() integration with the helper
# ---------------------------------------------------------------------------


class TestBootstrapAutoDerive:
    """``bootstrap()`` calls the helper when args are omitted."""

    def test_explicit_args_skip_derivation(
        self,
        _isolated_state: Path,
        _isolated_logging: None,
        _isolated_excepthook: None,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """If both args are passed explicitly, derivation is NOT
        called (saves stack walk + metadata lookup on the hot path)."""
        called = {"count": 0}

        def _fail_if_called(*args: object, **kwargs: object) -> tuple[None, None]:
            called["count"] += 1
            return (None, None)

        monkeypatch.setattr(
            "mgf.common._identity._derive_identity_from_metadata",
            _fail_if_called,
        )
        ctx = mgf.common.bootstrap(app_name="explicit", app_version="1.2.3")
        try:
            assert ctx.app_name == "explicit"
            assert ctx.app_version == "1.2.3"
            assert called["count"] == 0, "Derivation must NOT run when both args are explicit"
        finally:
            ctx.shutdown()

    def test_no_args_uses_derivation(
        self,
        _isolated_state: Path,
        _isolated_logging: None,
        _isolated_excepthook: None,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """``bootstrap()`` with zero positional args uses derivation."""
        monkeypatch.setattr(
            "mgf.common._identity._derive_identity_from_metadata",
            lambda main_module=None: ("derived-app", "9.8.7"),
        )
        ctx = mgf.common.bootstrap()
        try:
            assert ctx.app_name == "derived-app"
            assert ctx.app_version == "9.8.7"
        finally:
            ctx.shutdown()

    def test_partial_args_uses_derivation_for_missing_only(
        self,
        _isolated_state: Path,
        _isolated_logging: None,
        _isolated_excepthook: None,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """``bootstrap(app_name="x")`` derives only ``app_version``."""
        monkeypatch.setattr(
            "mgf.common._identity._derive_identity_from_metadata",
            lambda main_module=None: ("ignored-name", "9.8.7"),
        )
        ctx = mgf.common.bootstrap(app_name="explicit-name")
        try:
            assert ctx.app_name == "explicit-name"  # explicit wins
            assert ctx.app_version == "9.8.7"  # derived
        finally:
            ctx.shutdown()

    def test_warns_when_derivation_fails_and_no_explicit_args(
        self,
        _isolated_state: Path,
        _isolated_logging: None,
        _isolated_excepthook: None,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """When derivation returns (None, None) AND args were None,
        emit :class:`UnconfiguredWarning` and fall back to
        placeholders. Warning is actionable — names the explicit
        path."""
        monkeypatch.setattr(
            "mgf.common._identity._derive_identity_from_metadata",
            lambda main_module=None: (None, None),
        )
        with pytest.warns(UnconfiguredWarning, match="auto-derive identity"):
            ctx = mgf.common.bootstrap()
        try:
            assert ctx.app_name == "app"
            assert ctx.app_version == "unknown"
        finally:
            ctx.shutdown()

    def test_no_warning_when_derivation_succeeds(
        self,
        _isolated_state: Path,
        _isolated_logging: None,
        _isolated_excepthook: None,
        monkeypatch: pytest.MonkeyPatch,
        recwarn: pytest.WarningsRecorder,
    ) -> None:
        """Successful derivation MUST NOT emit
        :class:`UnconfiguredWarning` — silence on success is the
        ergonomic win."""
        monkeypatch.setattr(
            "mgf.common._identity._derive_identity_from_metadata",
            lambda main_module=None: ("ok-app", "1.0"),
        )
        ctx = mgf.common.bootstrap()
        try:
            unconfigured = [w for w in recwarn.list if issubclass(w.category, UnconfiguredWarning)]
            assert unconfigured == [], (
                f"UnconfiguredWarning emitted on a successful derivation: "
                f"{[str(w.message) for w in unconfigured]}"
            )
            assert ctx.app_name == "ok-app"
            assert ctx.app_version == "1.0"
        finally:
            ctx.shutdown()

    def test_returned_appcontext_is_what_current_context_returns(
        self,
        _isolated_state: Path,
        _isolated_logging: None,
        _isolated_excepthook: None,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Phase A's identity-fix invariant still holds with Phase B's
        derived identity: ``current_context() is bootstrap()``."""
        monkeypatch.setattr(
            "mgf.common._identity._derive_identity_from_metadata",
            lambda main_module=None: ("derived", "1.0"),
        )
        ctx = mgf.common.bootstrap()
        try:
            assert isinstance(ctx, AppContext)
            assert mgf.common.current_context() is ctx
        finally:
            ctx.shutdown()
