from __future__ import annotations

import os
import subprocess
import sys
from pathlib import Path

import nbformat
import pytest


ROOT = Path(__file__).resolve().parents[1]
STRUCTURED_NOTEBOOK_SOURCES = sorted((ROOT / "examples").glob("[0-9][0-9]_*/*.py"))
BANNED_EXAMPLE_PATTERNS = [
    "fuggers_py.traits",
    "fuggers_py.ext",
    "tests.",
    "sys.path.insert",
]


def _cell_signature(path: Path) -> list[tuple[str, str]]:
    jupytext = pytest.importorskip("jupytext")
    if path.suffix == ".py":
        notebook = jupytext.read(path)
    else:
        notebook = nbformat.read(path, as_version=4)
    return [
        (cell.cell_type, cell.source.strip())
        for cell in notebook.cells
        if cell.cell_type in {"markdown", "code"}
    ]


def test_structured_examples_exist() -> None:
    assert STRUCTURED_NOTEBOOK_SOURCES, "Expected structured example notebooks under examples/NN_topic/."
    assert len(STRUCTURED_NOTEBOOK_SOURCES) == 20, "Expected the complete 20-notebook structured catalog."


@pytest.mark.parametrize("source_path", STRUCTURED_NOTEBOOK_SOURCES, ids=lambda path: path.stem)
def test_structured_notebooks_have_synced_ipynb_pair(source_path: Path) -> None:
    notebook_path = source_path.with_suffix(".ipynb")
    assert notebook_path.exists(), f"Missing synced notebook for {source_path}"
    assert _cell_signature(source_path) == _cell_signature(notebook_path), f"{source_path} is out of sync with its ipynb pair"


@pytest.mark.parametrize("source_path", STRUCTURED_NOTEBOOK_SOURCES, ids=lambda path: path.stem)
def test_structured_notebooks_do_not_use_removed_or_internal_imports(source_path: Path) -> None:
    text = source_path.read_text(encoding="utf-8")
    for pattern in BANNED_EXAMPLE_PATTERNS:
        assert pattern not in text, f"{source_path} still contains banned pattern: {pattern}"


@pytest.mark.parametrize("source_path", STRUCTURED_NOTEBOOK_SOURCES, ids=lambda path: path.stem)
def test_structured_notebook_sources_execute(source_path: Path, tmp_path: Path) -> None:
    pytest.importorskip("pandas")
    pytest.importorskip("matplotlib")

    env = os.environ.copy()
    env["PYTHONPATH"] = os.pathsep.join([str(ROOT / "src"), str(ROOT)])
    env["MPLCONFIGDIR"] = str(tmp_path / "mplconfig")
    env["IPYTHONDIR"] = str(tmp_path / "ipython")

    completed = subprocess.run(
        [sys.executable, str(source_path)],
        cwd=ROOT,
        env=env,
        capture_output=True,
        text=True,
        timeout=120,
        check=False,
    )

    assert completed.returncode == 0, (
        f"{source_path} failed with exit code {completed.returncode}\n"
        f"stdout:\n{completed.stdout}\n"
        f"stderr:\n{completed.stderr}"
    )
