"""Tests for It/Projection system integration with different CrystFlux modes.

These tests verify that the It placeholder and Projection system work correctly
across all three semantic modes: strict, latent, and dream.
"""

from __future__ import annotations

import pytest

from crystflux.v1 import Crystallizer, it
from crystflux.v1.adapters import with_dream_mode


# ----------------------------------------------------------------------
# Strict mode tests
# ----------------------------------------------------------------------


class TestProjectionInStrictMode:
    """Test that projections work correctly in strict mode."""

    def test_projection_with_is_true_in_strict_mode(self) -> None:
        """Test is_true() works correctly in strict mode."""
        data = [{"active": True}, {"active": False}]
        strict_data = Crystallizer.strict(data).to_array()

        active = strict_data.filter(it.active.is_true())
        # Map and extract the active field values
        result = active.map(it.active).as_json_value()
        assert result == (True,)

    def test_projection_with_comparison_in_strict_mode(self) -> None:
        """Test numeric comparisons work in strict mode."""
        data = [{"age": 30}, {"age": 18}]
        strict_data = Crystallizer.strict(data).to_array()

        adults = strict_data.filter(it.age > 20)
        assert adults.map(it.age).as_int_array() == (30,)

    def test_projection_strict_mode_fails_on_missing_key(self) -> None:
        """Test strict mode raises on missing keys."""
        data = [{"active": True}, {}]  # Second item missing "active" key
        strict_data = Crystallizer.strict(data).to_array()

        # Strict mode should fail when accessing missing keys
        with pytest.raises(Exception):  # May raise KeyError or similar
            _ = list(strict_data.filter(it.active.is_true()).as_json_value())  # type: ignore[arg-type]


# ----------------------------------------------------------------------
# Latent mode tests
# ----------------------------------------------------------------------


class TestProjectionInLatentMode:
    """Test that projections work correctly in latent mode."""

    def test_projection_with_is_true_in_latent_mode(self) -> None:
        """Test is_true() handles missing keys gracefully in latent mode."""
        data = [{"active": True}, {}, {"active": False}]
        latent_data = Crystallizer.latent(data).to_array()

        active = latent_data.filter(it.active.is_true())
        # Map and extract the active field values
        result = active.map(it.active).as_json_value()
        assert result == (True,)

    def test_projection_with_comparison_in_latent_mode(self) -> None:
        """Test numeric comparisons handle missing values in latent mode."""
        data = [{"age": 30}, {}, {"age": 18}]
        latent_data = Crystallizer.latent(data).to_array()

        adults = latent_data.filter(it.age > 20)
        assert adults.map(it.age).as_int_array() == (30,)

    def test_projection_latent_mode_tolerates_missing_keys(self) -> None:
        """Test latent mode tolerates undefined fields."""
        data = [{"active": True}, {"missing": "field"}]
        latent_data = Crystallizer.latent(data).to_array()

        # Should handle missing fields gracefully
        result = latent_data.filter(it.active.is_true()).as_json_value()
        assert result == ({"active": True},)


# ----------------------------------------------------------------------
# Dream mode tests
# ----------------------------------------------------------------------


@with_dream_mode
class TestProjectionInDreamMode:
    """Test that projections work correctly in dream mode."""

    def test_projection_with_is_true_in_dream_mode(self) -> None:
        """Test is_true() works correctly in dream mode."""
        data = [{"active": True}, {"active": False}]
        dream_data = Crystallizer.dream(data).to_array()

        active = dream_data.filter(it.active.is_true())
        # Map and extract the active field values
        result = active.map(it.active).as_json_value()
        assert result == (True,)

    def test_projection_with_comparison_in_dream_mode(self) -> None:
        """Test numeric comparisons work in dream mode."""
        data = [{"age": 30}, {"age": 18}]
        dream_data = Crystallizer.dream(data).to_array()

        adults = dream_data.filter(it.age > 20)
        assert adults.map(it.age).as_int_array() == (30,)

    def test_projection_dream_mode_tolerates_undefined_fields(self) -> None:
        """Test dream mode tolerates undefined fields."""
        data = [{"active": True}, {"missing": "field"}]
        dream_data = Crystallizer.dream(data).to_array()

        # Should tolerate undefined fields
        result = dream_data.filter(it.active.is_true()).as_json_value()
        assert result == ({"active": True},)


# ----------------------------------------------------------------------
# Cross-mode comparison tests
# ----------------------------------------------------------------------


class TestProjectionAcrossModes:
    """Test projection behavior differences across modes."""

    def test_is_true_strict_vs_latent_with_missing_data(self) -> None:
        """Compare strict vs latent mode when handling missing data."""
        data = [{"active": True}, {}, {"active": False}]

        # Latent mode should handle missing keys gracefully
        latent_data = Crystallizer.latent(data).to_array()
        latent_active = latent_data.filter(it.active.is_true()).as_json_value()
        assert latent_active == ({"active": True},)

        # Strict mode should fail on missing keys
        strict_data = Crystallizer.strict(data).to_array()
        with pytest.raises(Exception):
            _ = list(strict_data.filter(it.active.is_true()).as_json_value())  # type: ignore[arg-type]

    def test_comparison_strict_vs_latent_with_missing_data(self) -> None:
        """Compare strict vs latent mode when comparing with missing data."""
        data = [{"age": 30}, {}, {"age": 18}]

        # Latent mode should handle missing values gracefully
        latent_data = Crystallizer.latent(data).to_array()
        latent_adults = latent_data.filter(it.age > 20)
        assert latent_adults.map(it.age).as_int_array() == (30,)

        # Strict mode should fail on missing keys
        strict_data = Crystallizer.strict(data).to_array()
        with pytest.raises(Exception):
            _ = list(strict_data.filter(it.age > 20).as_json_value())  # type: ignore[arg-type]
