"""Packaging + lazy-import discipline tests for ratatoskr.web per issue #16.

- `index.html` resolvable via importlib.resources (ships in wheel)
- `ratatoskr.web.entrypoint` importable without starlette+uvicorn,
  prints install-hint and exits non-zero in that mode
"""

from importlib.resources import files


def test_index_html_in_package() -> None:
    """static/index.html is locatable via importlib.resources.

    INV-009 packaging discipline. The asset must be part of the
    installed package — `_static_dir()` in the server uses this exact
    resolution path at startup.
    """
    path = files("ratatoskr.web") / "static" / "index.html"
    assert path.is_file(), f"index.html missing at {path}"
    content = path.read_text()
    assert "<html" in content
    assert "ratatoskr-web" in content


def test_entrypoint_no_top_level_starlette_import() -> None:
    """INV-001: importing `ratatoskr.web.entrypoint` MUST NOT import
    starlette or uvicorn at the module level. Verified by AST inspection
    of the source — checks no top-level `import starlette` /
    `from starlette` / `import uvicorn` / `from uvicorn` statements.
    """
    import ast
    from importlib.resources import files

    src = (files("ratatoskr.web") / "entrypoint.py").read_text()
    tree = ast.parse(src)
    banned = {"starlette", "uvicorn"}
    for node in tree.body:
        if isinstance(node, ast.Import):
            for alias in node.names:
                top = alias.name.split(".")[0]
                assert top not in banned, (
                    f"top-level `import {alias.name}` violates INV-001 "
                    "lazy-import discipline"
                )
        elif isinstance(node, ast.ImportFrom):
            mod = (node.module or "").split(".")[0]
            assert mod not in banned, (
                f"top-level `from {node.module} import ...` violates "
                "INV-001 lazy-import discipline"
            )


def test_entrypoint_missing_api_key_returns_11(monkeypatch) -> None:
    """missing_api_key [error]: WORLDTREE_API_KEY unset → exit 11."""
    monkeypatch.delenv("WORLDTREE_API_KEY", raising=False)
    from ratatoskr.web.entrypoint import main

    rc = main(["--port", "0"])
    assert rc == 11


def test_entrypoint_missing_extras_returns_12(monkeypatch) -> None:
    """missing_extras [error]: starlette unimportable → exit 12.

    v0.16.0: the missing-extras probe is scoped to the OPTIONAL extras
    (starlette / uvicorn) only. Simulated by shadowing `starlette` to
    None in sys.modules so its import raises ImportError inside the
    narrow try-block.
    """
    import sys

    monkeypatch.setenv("WORLDTREE_API_KEY", "k")
    monkeypatch.setenv("WORLDTREE_API_URL", "https://example.com")
    monkeypatch.setitem(sys.modules, "starlette", None)

    from ratatoskr.web.entrypoint import main

    rc = main(["--port", "0"])
    assert rc == 12


def test_entrypoint_real_import_bug_propagates(monkeypatch) -> None:
    """v0.16.0: an ImportError from a BASELINE module (not an extra) MUST
    propagate as a real traceback, not be masked as exit-12 missing-extras.
    Shadowing ratatoskr.web.server (a first-party module, present with or
    without the [web] extras) should raise, not return 12.
    """
    import sys

    import pytest as _pytest

    monkeypatch.setenv("WORLDTREE_API_KEY", "k")
    monkeypatch.setenv("WORLDTREE_API_URL", "https://example.com")
    monkeypatch.setitem(sys.modules, "ratatoskr.web.server", None)

    from ratatoskr.web.entrypoint import main

    with _pytest.raises(ImportError):
        main(["--port", "0"])


class TestEntrypointArgs:
    """FN main argparse + serve-loop traces (contract TESTS)."""

    def test_default_host_is_zero(self) -> None:
        """default_host_is_zero [trace]: argv=[] → host == '0.0.0.0'."""
        from ratatoskr.web.entrypoint import _build_arg_parser
        args = _build_arg_parser().parse_args([])
        assert args.host == "0.0.0.0"
        assert args.port == 8765
        assert args.open is False

    def test_port_zero_supported(self) -> None:
        """port_zero_supported [trace]: argv=['--port','0'] → port == 0."""
        from ratatoskr.web.entrypoint import _build_arg_parser
        args = _build_arg_parser().parse_args(["--port", "0"])
        assert args.port == 0

    def test_happy_argv_serves_and_returns_zero(self, monkeypatch) -> None:
        """happy_argv [tracer]: env set + uvicorn.run mocked → main returns 0."""
        import uvicorn
        monkeypatch.setenv("WORLDTREE_API_KEY", "k")
        monkeypatch.setenv("WORLDTREE_API_URL", "https://example.com")
        calls = {}
        monkeypatch.setattr(uvicorn, "run", lambda app, **kw: calls.update(kw))
        from ratatoskr.web.entrypoint import main
        rc = main(["--port", "0"])
        assert rc == 0
        assert calls["host"] == "0.0.0.0"
        assert calls["port"] == 0

    def test_open_flag_calls_webbrowser(self, monkeypatch) -> None:
        """open_flag_calls_webbrowser [trace]: --open → webbrowser.open called."""
        import uvicorn
        import webbrowser
        monkeypatch.setenv("WORLDTREE_API_KEY", "k")
        monkeypatch.setattr(uvicorn, "run", lambda app, **kw: None)
        opened = []
        monkeypatch.setattr(webbrowser, "open", lambda url: opened.append(url))
        from ratatoskr.web.entrypoint import main
        main(["--port", "0", "--open"])
        assert len(opened) == 1

    def test_no_open_default(self, monkeypatch) -> None:
        """no_open_default [trace]: argv without --open → webbrowser.open NOT called."""
        import uvicorn
        import webbrowser
        monkeypatch.setenv("WORLDTREE_API_KEY", "k")
        monkeypatch.setattr(uvicorn, "run", lambda app, **kw: None)
        opened = []
        monkeypatch.setattr(webbrowser, "open", lambda url: opened.append(url))
        from ratatoskr.web.entrypoint import main
        main(["--port", "0"])
        assert opened == []
