"""Tests for PythonState lazy accessor methods."""

from __future__ import annotations

import ast
from pathlib import Path
from unittest.mock import MagicMock

import pytest

from codeflash_python._state import PythonState


@pytest.fixture(name="state")
def _state() -> PythonState:
    """Build a PythonState with a minimal mock configuration."""
    cfg = MagicMock()
    cfg.project_root = Path("/fake/project")
    return PythonState(cfg=cfg)


class TestModuleAst:
    """Tests for PythonState.module_ast."""

    def test_valid_file_returns_ast(self, state, tmp_path) -> None:
        """Valid Python source is parsed and cached."""
        py_file = tmp_path / "mod.py"
        py_file.write_text("x = 1\n", encoding="utf-8")

        result = state.module_ast(py_file)

        assert isinstance(result, ast.Module)

    def test_cached_on_second_call(self, state, tmp_path) -> None:
        """Second call returns the same cached object."""
        py_file = tmp_path / "mod.py"
        py_file.write_text("x = 1\n", encoding="utf-8")

        first = state.module_ast(py_file)
        second = state.module_ast(py_file)

        assert first is second

    def test_syntax_error_returns_none(self, state, tmp_path) -> None:
        """Malformed source returns None instead of crashing."""
        py_file = tmp_path / "bad.py"
        py_file.write_text("def (broken:\n", encoding="utf-8")

        result = state.module_ast(py_file)

        assert result is None

    def test_syntax_error_not_cached(self, state, tmp_path) -> None:
        """Failed parse is not cached — a corrected file can be re-read."""
        py_file = tmp_path / "fix.py"
        py_file.write_text("def (broken:\n", encoding="utf-8")

        assert state.module_ast(py_file) is None

        py_file.write_text("x = 1\n", encoding="utf-8")
        result = state.module_ast(py_file)

        assert isinstance(result, ast.Module)


class TestInvalidateModule:
    """Tests for PythonState.invalidate_module."""

    def test_invalidate_clears_cached_ast(self, state, tmp_path) -> None:
        """After invalidation, the next call re-parses."""
        py_file = tmp_path / "mod.py"
        py_file.write_text("x = 1\n", encoding="utf-8")

        first = state.module_ast(py_file)
        state.invalidate_module(py_file)

        py_file.write_text("y = 2\n", encoding="utf-8")
        second = state.module_ast(py_file)

        assert first is not second
        assert "y" in ast.unparse(second)

    def test_invalidate_nonexistent_path_is_noop(self, state) -> None:
        """Invalidating a path that was never cached does not raise."""
        state.invalidate_module(Path("/nonexistent/mod.py"))
