"""Tests for init=False dataclass field handling.

These tests verify that fields marked with init=False are correctly
excluded from both runtime Config generation and stub generation.
"""

from __future__ import annotations

from dataclasses import dataclass, field

from hipr import Hyper, configurable


class TestInitFalseRuntime:
  """Runtime tests for init=False field handling."""

  def test_init_false_field_excluded_from_config(self) -> None:
    """Fields with init=False should not appear in Config."""

    @configurable
    @dataclass
    class MyDataclass:
      included: Hyper[int] = 10
      excluded: int = field(default=5, init=False)

    # Config should only have 'included'
    config = MyDataclass.Config(included=20)
    assert hasattr(config, "included")
    assert not hasattr(config, "excluded")

  def test_init_false_field_not_in_config_init(self) -> None:
    """Config __init__ should not accept init=False fields."""

    @configurable
    @dataclass
    class MyDataclass:
      a: Hyper[int] = 1
      computed: str = field(default="computed", init=False)

    # Should work - only 'a' is accepted
    config = MyDataclass.Config(a=5)
    instance = config.make()
    assert instance.a == 5
    # The computed field gets its default value
    assert instance.computed == "computed"

  def test_init_false_with_factory(self) -> None:
    """Fields with init=False and default_factory should be excluded."""

    @configurable
    @dataclass
    class MyDataclass:
      values: Hyper[list[int]] = field(default_factory=list)
      cache: dict[str, int] = field(default_factory=dict, init=False)

    config = MyDataclass.Config(values=[1, 2, 3])
    assert hasattr(config, "values")
    assert not hasattr(config, "cache")

    instance = config.make()
    assert instance.values == [1, 2, 3]
    assert instance.cache == {}

  def test_mixed_init_fields(self) -> None:
    """Test dataclass with mix of init=True and init=False fields."""

    @configurable
    @dataclass
    class MixedClass:
      name: Hyper[str] = "default"
      count: Hyper[int] = 0
      internal_state: str = field(default="state", init=False)
      computed_value: int = field(default=0, init=False)

    config = MixedClass.Config(name="test", count=5)
    instance = config.make()

    assert instance.name == "test"
    assert instance.count == 5
    assert instance.internal_state == "state"
    assert instance.computed_value == 0


class TestInitFalseStubGeneration:
  """Tests for init=False handling in stub generation."""

  def test_stub_excludes_init_false_hyper(self) -> None:
    """Stub extraction should skip init=False Hyper fields."""
    import ast

    from hipr.stubs.extraction import extract_hyperparams_from_dataclass

    source = """
@dataclass
class Test:
    included: Hyper[int] = 10
    excluded: Hyper[int] = field(default=5, init=False)
"""
    tree = ast.parse(source)
    class_node = tree.body[0]
    assert isinstance(class_node, ast.ClassDef)

    params, _ = extract_hyperparams_from_dataclass(class_node)
    param_names = [p.name for p in params]

    assert "included" in param_names
    assert "excluded" not in param_names

  def test_stub_excludes_init_false_all_params(self) -> None:
    """extract_all_params_from_class should skip init=False fields."""
    import ast

    from hipr.stubs.extraction import extract_all_params_from_class

    source = """
@dataclass
class Test:
    name: str = "default"
    computed: str = field(default="x", init=False)
"""
    tree = ast.parse(source)
    class_node = tree.body[0]
    assert isinstance(class_node, ast.ClassDef)

    all_params = extract_all_params_from_class(class_node)
    param_names = [name for name, _, _ in all_params]

    assert "name" in param_names
    assert "computed" not in param_names
