"""
S1.3 — LLM isolation gate.

Confirms that markitect.llm.* has zero imports from markitect.prompts.*
or markitect.infospace.*, making the module safe to extract into a
standalone llm-connect library.

These tests must pass before extraction (S2).
"""

import importlib
import pkgutil
import sys
from pathlib import Path


def _collect_llm_modules() -> list[str]:
    """Return fully-qualified names of all modules under markitect.llm."""
    import markitect.llm as pkg
    pkg_path = Path(pkg.__file__).parent
    names = []
    for info in pkgutil.walk_packages([str(pkg_path)], prefix="markitect.llm."):
        names.append(info.name)
    # Include the package itself
    names.insert(0, "markitect.llm")
    return names


def _direct_imports(module_name: str) -> set[str]:
    """Return set of top-level module names imported by *module_name*."""
    mod = importlib.import_module(module_name)
    src_file = getattr(mod, "__file__", None)
    if not src_file or not src_file.endswith(".py"):
        return set()

    imports: set[str] = set()
    with open(src_file) as f:
        for line in f:
            stripped = line.strip()
            if stripped.startswith("from ") or stripped.startswith("import "):
                # Extract the root package of the imported name
                parts = stripped.split()
                if parts[0] == "from" and len(parts) >= 2:
                    imports.add(parts[1].split(".")[0] + "." + parts[1].split(".")[1]
                                if "." in parts[1] else parts[1])
                    # Also capture full dotted path for cross-module check
                    imports.add(parts[1])
    return imports


def _import_lines(src_file: str) -> list[str]:
    """Return only import-statement lines from a Python source file."""
    lines = []
    with open(src_file) as f:
        for line in f:
            stripped = line.strip()
            if stripped.startswith("from ") or stripped.startswith("import "):
                lines.append(stripped)
    return lines


def test_no_prompts_import_in_llm_tree():
    """markitect.llm must not import anything from markitect.prompts.*"""
    violations = []
    for mod_name in _collect_llm_modules():
        try:
            mod = importlib.import_module(mod_name)
        except ImportError:
            continue
        src_file = getattr(mod, "__file__", None)
        if not src_file or not src_file.endswith(".py"):
            continue
        for line in _import_lines(src_file):
            if "markitect.prompts" in line:
                violations.append(mod_name)
                break

    assert violations == [], (
        f"These llm modules still import from markitect.prompts: {violations}"
    )


def test_no_infospace_import_in_llm_tree():
    """markitect.llm must not import anything from markitect.infospace.*"""
    violations = []
    for mod_name in _collect_llm_modules():
        try:
            mod = importlib.import_module(mod_name)
        except ImportError:
            continue
        src_file = getattr(mod, "__file__", None)
        if not src_file or not src_file.endswith(".py"):
            continue
        for line in _import_lines(src_file):
            if "markitect.infospace" in line:
                violations.append(mod_name)
                break

    assert violations == [], (
        f"These llm modules still import from markitect.infospace: {violations}"
    )


def test_runconfig_and_llmresponse_canonical_in_llm():
    """RunConfig and LLMResponse must be defined in markitect.llm.models."""
    from markitect.llm.models import RunConfig, LLMResponse

    assert RunConfig.__module__ == "markitect.llm.models", (
        f"RunConfig.module = {RunConfig.__module__!r}, expected 'markitect.llm.models'"
    )
    assert LLMResponse.__module__ == "markitect.llm.models", (
        f"LLMResponse.module = {LLMResponse.__module__!r}, expected 'markitect.llm.models'"
    )


def test_llmadapter_canonical_in_llm():
    """LLMAdapter must be defined in markitect.llm.adapter."""
    from markitect.llm.adapter import LLMAdapter

    assert LLMAdapter.__module__ == "markitect.llm.adapter", (
        f"LLMAdapter.module = {LLMAdapter.__module__!r}, expected 'markitect.llm.adapter'"
    )


def test_backward_compat_prompts_reexport():
    """markitect.prompts.execution.models must still export RunConfig/LLMResponse."""
    from markitect.prompts.execution.models import RunConfig, LLMResponse
    from markitect.llm.models import RunConfig as RC, LLMResponse as LR

    assert RunConfig is RC, "prompts re-export RunConfig must be the same object as llm.models.RunConfig"
    assert LLMResponse is LR, "prompts re-export LLMResponse must be the same object as llm.models.LLMResponse"


def test_backward_compat_llmadapter_reexport():
    """markitect.prompts.execution.llm_adapter must still export LLMAdapter."""
    from markitect.prompts.execution.llm_adapter import LLMAdapter
    from markitect.llm.adapter import LLMAdapter as LA

    assert LLMAdapter is LA, "prompts re-export LLMAdapter must be the same object as llm.adapter.LLMAdapter"


def test_app_name_parameterization():
    """resolve_llm(app_name=X) uses ~/.config/X/config.toml and X_HELPER_MODEL."""
    from markitect.llm.toml_config import (
        _model_env_var,
        _user_config_path,
        _dir_config_name,
        resolve_llm,
    )

    assert _model_env_var("railiance") == "RAILIANCE_HELPER_MODEL"
    assert _model_env_var("markitect") == "MARKITECT_HELPER_MODEL"
    assert str(_user_config_path("railiance")).endswith(".config/railiance/config.toml")
    assert _dir_config_name("railiance") == ".railiance.toml"

    # Smoke: resolve falls back to hardcoded for unknown app
    r = resolve_llm(app_name="nonexistent_app_xyz")
    assert r.provider_source == "hardcoded"
    assert r.model_source == "hardcoded"
