"""Regression tests for docstring propagation (Runtime and Stubs)."""

from pathlib import Path
from textwrap import dedent

from nonfig import configurable
from nonfig.stubs.generator import generate_stub_content
from nonfig.stubs.scanner import scan_module

# --- Runtime Tests ---


def test_runtime_function_docstring_propagation():
  """Test that function docstrings are propagated to Config at runtime."""

  @configurable
  def my_func(x: int = 1):
    """Original function docstring."""
    return x

  assert my_func.Config.__doc__ is not None
  assert "Configuration for my_func." in my_func.Config.__doc__
  assert "Original function docstring." in my_func.Config.__doc__


def test_runtime_class_docstring_propagation():
  """Test that class docstrings are propagated to Config at runtime."""

  @configurable
  class MyClass:
    """Original class docstring."""

    x: int = 1

  assert MyClass.Config.__doc__ is not None
  assert "Configuration for MyClass." in MyClass.Config.__doc__
  assert "Original class docstring." in MyClass.Config.__doc__


def test_runtime_no_docstring():
  """Test behavior when no docstring is present."""

  @configurable
  def no_doc(x: int = 1):
    pass

  # Should currently be None or just have the prefix?
  # Based on implementation: if func.__doc__ is None, we don't set config_cls.__doc__
  # Wait, looking at implementation:
  # if func.__doc__: config_cls.__doc__ = ...
  # So if no docstring, Config docstring remains None (or pydantic default)?
  # Pydantic models might have a default docstring.

  # If the user implementation only sets it IF original exists, then we verify that.
  # But usually we would want at least the "Configuration for X" part.
  # Checking implementation:
  #   if func.__doc__: config_cls.__doc__ = f"Configuration for {func.__name__}.\n\n{func.__doc__}"
  # So if no docstring, it is NOT set.

  # Verify that it doesn't crash or error
  assert True


# --- Stub Generation Tests ---


def test_stub_docstring_function(tmp_path: Path):
  """Test docstring propagation in function stubs."""
  source_file = tmp_path / "test_func.py"
  source_file.write_text(
    dedent("""
        from nonfig import configurable, Hyper

        @configurable
        def my_func(x: Hyper[int] = 1):
            \"\"\"Original function docstring.

            Args:
                x: A parameter.
            \"\"\"
            pass
    """)
  )

  infos, aliases = scan_module(source_file)
  content = generate_stub_content(infos, source_file, aliases)

  # 1. Check Config class docstring
  assert "class _my_func_Config(_NCMakeableModel" in content
  assert "Configuration class for my_func." in content
  assert "Original function docstring." in content

  # 2. Check Config.__init__ docstring
  assert "def __init__(self" in content
  assert "Initialize configuration for my_func." in content
  assert "Configuration:" in content
  assert "x (int)" in content


def test_stub_docstring_class(tmp_path: Path):
  """Test docstring propagation in class stubs."""
  source_file = tmp_path / "test_class.py"
  source_file.write_text(
    dedent("""
        from nonfig import configurable, Hyper

        @configurable
        class MyClass:
            \"\"\"Original class docstring.\"\"\"
            x: int = 1
    """)
  )

  infos, aliases = scan_module(source_file)
  content = generate_stub_content(infos, source_file, aliases)

  # 1. Check Config class docstring
  assert "class Config(_NCMakeableModel" in content
  assert "Configuration class for MyClass." in content
  assert "Original class docstring." in content

  # 2. Check Config.__init__ docstring
  assert "def __init__(self" in content
  assert "Initialize configuration for MyClass." in content
  assert "x (int)" in content


def test_stub_no_docstring(tmp_path: Path):
  """Test stub generation when original has no docstring."""
  source_file = tmp_path / "test_no_doc.py"
  source_file.write_text(
    dedent("""
        from nonfig import configurable, Hyper

        @configurable
        def no_doc(x: Hyper[int] = 1):
            pass
    """)
  )

  infos, aliases = scan_module(source_file)
  content = generate_stub_content(infos, source_file, aliases)

  # Should still generate "Configuration class for..." header
  assert "Configuration class for no_doc." in content


def test_stub_docstring_with_annotated_hyper(tmp_path: Path):
  """Test verification for Annotated[T, Hyper] docstring generation."""
  source_file = tmp_path / "test_annotated.py"
  source_file.write_text(
    dedent("""
        from typing import Annotated
        from nonfig import configurable, Hyper

        @configurable
        def my_func(x: Annotated[int, Hyper] = 1):
            \"\"\"Docstring.\"\"\"
            pass
    """)
  )

  infos, aliases = scan_module(source_file)
  content = generate_stub_content(infos, source_file, aliases)

  # Should find the parameter and include it in docstring
  assert "x (int)" in content


def test_stub_docstring_with_complex_types(tmp_path: Path):
  """Test docstring generation for complex types."""
  source_file = tmp_path / "test_complex.py"
  source_file.write_text(
    dedent("""
        from nonfig import configurable, Hyper
        from typing import Literal

        @configurable
        def my_func(mode: Hyper[Literal['a', 'b']] = 'a'):
            \"\"\"Docstring.\"\"\"
            pass
    """)
  )

  infos, aliases = scan_module(source_file)
  content = generate_stub_content(infos, source_file, aliases)

  # Primitive transformation might simplify this or keep it as is.
  # We want to ensure it doesn't crash and output looks reasonable.
  assert "mode: Literal['a', 'b']" in content or "mode: str" in content
