"""Unit tests for ReBAC batch operations.

Tests cover:
- rebac_write_batch() basic functionality
- Batch deduplication
- Transaction rollback on failure
- Cross-zone validation in batches
- Cycle detection in batches
- Cache invalidation after batch writes
- Performance compared to individual writes
"""

import pytest

pytest.importorskip("pyroaring")

from sqlalchemy import create_engine

from nexus.bricks.rebac.consistency.metastore_namespace_store import MetastoreNamespaceStore
from nexus.bricks.rebac.manager import ReBACManager
from nexus.storage.models import Base
from tests.testkit.metadata import InMemoryNexusFS


@pytest.fixture
def engine():
    """Create in-memory SQLite database for testing."""
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    return engine


@pytest.fixture
def rebac_manager(engine):
    """Create a ReBAC manager for testing."""
    manager = ReBACManager(
        engine=engine,
        cache_ttl_seconds=300,
        max_depth=10,
        namespace_store=MetastoreNamespaceStore(InMemoryNexusFS()),
    )
    yield manager
    manager.close()


def test_batch_write_basic(rebac_manager):
    """Test basic batch write functionality."""
    # Create batch of tuples
    tuples = [
        {
            "subject": ("file", "/a/b/c.txt"),
            "relation": "parent",
            "object": ("file", "/a/b"),
            "zone_id": "org_123",
        },
        {
            "subject": ("file", "/a/b"),
            "relation": "parent",
            "object": ("file", "/a"),
            "zone_id": "org_123",
        },
        {
            "subject": ("file", "/a"),
            "relation": "parent",
            "object": ("file", "/"),
            "zone_id": "org_123",
        },
    ]

    created = rebac_manager.rebac_write_batch(tuples)
    assert created == 3

    # Verify tuples were created
    assert rebac_manager.rebac_check(
        subject=("file", "/a/b/c.txt"),
        permission="parent",
        object=("file", "/a/b"),
        zone_id="org_123",
    )
    assert rebac_manager.rebac_check(
        subject=("file", "/a/b"),
        permission="parent",
        object=("file", "/a"),
        zone_id="org_123",
    )


def test_batch_write_deduplication(rebac_manager):
    """Test that batch write handles duplicates correctly (idempotent)."""
    tuples = [
        {
            "subject": ("file", "/a/b"),
            "relation": "parent",
            "object": ("file", "/a"),
            "zone_id": "org_123",
        },
    ]

    # First batch
    created1 = rebac_manager.rebac_write_batch(tuples)
    assert created1 == 1

    # Second batch with same tuple (should skip duplicate)
    created2 = rebac_manager.rebac_write_batch(tuples)
    assert created2 == 0


def test_batch_write_multiple_duplicates_in_same_batch(rebac_manager):
    """Test that duplicates within the same batch are handled correctly."""
    tuples = [
        {
            "subject": ("file", "/a/b"),
            "relation": "parent",
            "object": ("file", "/a"),
            "zone_id": "org_123",
        },
        {
            "subject": ("file", "/c/d"),
            "relation": "parent",
            "object": ("file", "/c"),
            "zone_id": "org_123",
        },
    ]

    created = rebac_manager.rebac_write_batch(tuples)
    # Both should be created (they're different tuples)
    assert created == 2


def test_batch_write_empty_list(rebac_manager):
    """Test that empty batch returns 0."""
    created = rebac_manager.rebac_write_batch([])
    assert created == 0


def test_batch_write_cross_zone_validation(rebac_manager):
    """Test that cross-zone relationships are rejected in batch."""
    tuples = [
        {
            "subject": ("file", "/a/b"),
            "relation": "parent",
            "object": ("file", "/a"),
            "zone_id": "org_123",
            "subject_zone_id": "org_456",  # Different zone!
        },
    ]

    with pytest.raises(ValueError, match="Cross-zone relationship not allowed"):
        rebac_manager.rebac_write_batch(tuples)


def test_batch_write_cycle_detection(rebac_manager):
    """Test that cycles are detected and rejected in batch."""
    # Create initial relationship: /a -> /b
    rebac_manager.rebac_write(
        subject=("file", "/a"),
        relation="parent",
        object=("file", "/b"),
        zone_id="org_123",
    )

    # Try to create cycle: /b -> /a (would create cycle)
    tuples = [
        {
            "subject": ("file", "/b"),
            "relation": "parent",
            "object": ("file", "/a"),
            "zone_id": "org_123",
        },
    ]

    # Batch write should skip the cycle-creating tuple
    created = rebac_manager.rebac_write_batch(tuples)
    assert created == 0  # Skipped due to cycle

    # Verify cycle was not created
    assert not rebac_manager.rebac_check(
        subject=("file", "/b"),
        permission="parent",
        object=("file", "/a"),
        zone_id="org_123",
    )


def test_batch_write_mixed_valid_and_cycle(rebac_manager):
    """Test batch with mix of valid tuples and cycle-creating tuples."""
    # Create initial relationship
    rebac_manager.rebac_write(
        subject=("file", "/a"),
        relation="parent",
        object=("file", "/b"),
        zone_id="org_123",
    )

    tuples = [
        # Valid tuple
        {
            "subject": ("file", "/c"),
            "relation": "parent",
            "object": ("file", "/d"),
            "zone_id": "org_123",
        },
        # Cycle-creating tuple (should be skipped)
        {
            "subject": ("file", "/b"),
            "relation": "parent",
            "object": ("file", "/a"),
            "zone_id": "org_123",
        },
    ]

    created = rebac_manager.rebac_write_batch(tuples)
    assert created == 1  # Only the valid one

    # Verify valid tuple was created
    assert rebac_manager.rebac_check(
        subject=("file", "/c"),
        permission="parent",
        object=("file", "/d"),
        zone_id="org_123",
    )


def test_batch_write_cache_invalidation(rebac_manager):
    """Test that batch write invalidates cache correctly."""
    # Create a tuple and check it (populate cache)
    rebac_manager.rebac_write(
        subject=("file", "/a/b"),
        relation="parent",
        object=("file", "/a"),
        zone_id="org_123",
    )

    result1 = rebac_manager.rebac_check(
        subject=("file", "/a/b"),
        permission="parent",
        object=("file", "/a"),
        zone_id="org_123",
    )
    assert result1 is True

    # Delete the tuple using batch
    # (We can't test cache invalidation directly, but we can verify consistency)
    # For now, verify that subsequent checks work correctly
    tuples = [
        {
            "subject": ("file", "/a/b/c"),
            "relation": "parent",
            "object": ("file", "/a/b"),
            "zone_id": "org_123",
        },
    ]

    rebac_manager.rebac_write_batch(tuples)

    # Verify new tuple is visible
    result2 = rebac_manager.rebac_check(
        subject=("file", "/a/b/c"),
        permission="parent",
        object=("file", "/a/b"),
        zone_id="org_123",
    )
    assert result2 is True


def test_batch_write_with_userset_subject(rebac_manager):
    """Test batch write with userset-as-subject (3-tuple subject)."""
    tuples = [
        {
            "subject": ("group", "eng-team", "member"),
            "relation": "can_view",
            "object": ("file", "/docs"),
            "zone_id": "org_123",
        },
    ]

    created = rebac_manager.rebac_write_batch(tuples)
    assert created == 1


def test_batch_write_large_batch(rebac_manager):
    """Test batch write with large number of tuples (performance test)."""
    # Create 1000 unique parent tuples
    tuples = []
    for i in range(1000):
        tuples.append(
            {
                "subject": ("file", f"/files/file_{i}.txt"),
                "relation": "parent",
                "object": ("file", "/files"),
                "zone_id": "org_123",
            }
        )

    created = rebac_manager.rebac_write_batch(tuples)
    assert created == 1000

    # Verify a few tuples
    assert rebac_manager.rebac_check(
        subject=("file", "/files/file_0.txt"),
        permission="parent",
        object=("file", "/files"),
        zone_id="org_123",
    )
    assert rebac_manager.rebac_check(
        subject=("file", "/files/file_999.txt"),
        permission="parent",
        object=("file", "/files"),
        zone_id="org_123",
    )


def test_batch_write_different_zones(rebac_manager):
    """Test batch write with tuples from different zones."""
    tuples = [
        {
            "subject": ("file", "/a/b"),
            "relation": "parent",
            "object": ("file", "/a"),
            "zone_id": "org_123",
        },
        {
            "subject": ("file", "/x/y"),
            "relation": "parent",
            "object": ("file", "/x"),
            "zone_id": "org_456",
        },
    ]

    created = rebac_manager.rebac_write_batch(tuples)
    assert created == 2

    # Verify isolation
    assert rebac_manager.rebac_check(
        subject=("file", "/a/b"),
        permission="parent",
        object=("file", "/a"),
        zone_id="org_123",
    )
    assert rebac_manager.rebac_check(
        subject=("file", "/x/y"),
        permission="parent",
        object=("file", "/x"),
        zone_id="org_456",
    )

    # Note: In the current implementation, zone isolation is enforced at write time
    # via cross-zone validation, not at check time. The check above verifies that
    # tuples were created correctly for each zone.


@pytest.mark.skip(reason="Performance tests are flaky in CI")
def test_batch_write_performance_vs_individual(rebac_manager):
    """Compare performance of batch write vs individual writes."""
    import time

    tuples = [
        {
            "subject": ("file", f"/files/file_{i}.txt"),
            "relation": "parent",
            "object": ("file", "/files"),
            "zone_id": "org_123",
        }
        for i in range(100)
    ]

    # Batch write
    start = time.time()
    rebac_manager.rebac_write_batch(tuples)
    batch_time = time.time() - start

    # Clear database for fair comparison
    with rebac_manager._connection() as conn:
        cursor = rebac_manager._create_cursor(conn)
        cursor.execute("DELETE FROM rebac_tuples")
        conn.commit()

    # Individual writes
    start = time.time()
    for t in tuples:
        rebac_manager.rebac_write(
            subject=t["subject"],
            relation=t["relation"],
            object=t["object"],
            zone_id=t["zone_id"],
        )
    individual_time = time.time() - start

    # Batch should be significantly faster (at least 2x)
    print(f"Batch time: {batch_time:.3f}s, Individual time: {individual_time:.3f}s")
    # Note: Performance tests are unreliable in unit tests due to many factors
    # The main benefit is reduced connection usage, verified in integration tests
