import pytest
from atomsql import Database, Model, StringField, Session


class SessionUser(Model):
    name = StringField()
    email = StringField()


class SessionPost(Model):
    title = StringField()
    content = StringField()


@pytest.fixture
def db():
    """Create an in-memory SQLite database."""
    database = Database("sqlite://:memory:")
    database.create_all()
    return database


@pytest.fixture
def session(db):
    """Create a session for testing."""
    return Session(db)


def test_session_context_manager(db):
    """Test that Session works as a context manager."""
    with Session(db) as session:
        user = SessionUser(name="Alice", email="alice@example.com")
        session.add(user)

    # After commit, user should have an ID
    assert user.id is not None

    # Verify it was saved to database
    retrieved = SessionUser.get(user.id)
    assert retrieved.name == "Alice"
    assert retrieved.email == "alice@example.com"


def test_session_add_new_instance(session, db):
    """Test adding a new instance to the session."""
    user = SessionUser(name="Bob", email="bob@example.com")
    session.add(user)

    # Not yet in database
    assert user.id is None

    session.commit()

    # Now has an ID
    assert user.id is not None

    # Verify in database
    retrieved = SessionUser.get(user.id)
    assert retrieved.name == "Bob"


def test_session_add_multiple_instances(session, db):
    """Test adding multiple instances in one session."""
    user1 = SessionUser(name="User1", email="user1@example.com")
    user2 = SessionUser(name="User2", email="user2@example.com")

    session.add(user1)
    session.add(user2)
    session.commit()

    assert user1.id is not None
    assert user2.id is not None
    assert user1.id != user2.id


def test_session_delete(session, db):
    """Test deleting an instance through session."""
    # First create and save a user
    user = SessionUser(name="ToDelete", email="delete@example.com")
    session.add(user)
    session.commit()

    user_id = user.id
    assert user_id is not None

    # Now delete it
    session.delete(user)
    session.commit()

    # Should not be in database
    retrieved = SessionUser.get(user_id)
    assert retrieved is None


def test_session_rollback_on_exception(db):
    """Test that session rolls back on exception."""
    user = SessionUser(name="WillFail", email="fail@example.com")

    try:
        with Session(db) as session:
            session.add(user)
            raise ValueError("Test exception")
    except ValueError:
        pass

    # User should NOT have been saved
    assert user.id is None


def test_session_identity_map(session, db):
    """Test that identity map returns same instance for same ID."""
    # Create and save a user
    user = SessionUser(name="IdentityTest", email="identity@example.com")
    session.add(user)
    session.commit()

    user_id = user.id

    # Get the same user - should return same object
    retrieved1 = session.get(SessionUser, user_id)
    retrieved2 = session.get(SessionUser, user_id)

    assert retrieved1 is retrieved2  # Same object in memory


def test_session_get_nonexistent(session, db):
    """Test getting a nonexistent record returns None."""
    result = session.get(SessionUser, 99999)
    assert result is None


def test_session_query(session, db):
    """Test creating queries through session."""
    # Add some users
    user1 = SessionUser(name="QueryUser1", email="query1@example.com")
    user2 = SessionUser(name="QueryUser2", email="query2@example.com")

    session.add(user1)
    session.add(user2)
    session.commit()

    # Query through session
    results = list(session.query(SessionUser))
    assert len(results) >= 2


def test_session_manual_close(session, db):
    """Test manually closing a session."""
    user = SessionUser(name="ManualClose", email="manual@example.com")
    session.add(user)
    session.commit()
    session.close()

    # Session should not be active
    assert not session._is_active


def test_session_current(db):
    """Test get_current() returns active session."""
    assert Session.get_current() is None

    with Session(db) as session:
        assert Session.get_current() is session

    assert Session.get_current() is None


def test_session_flush(session, db):
    """Test flush executes SQL without committing."""
    user = SessionUser(name="FlushTest", email="flush@example.com")
    session.add(user)
    session.flush()

    # User should have ID after flush
    assert user.id is not None

    # But if we rollback, changes would be lost
    # (In a real transaction scenario)


def test_session_multiple_models(session, db):
    """Test session with multiple model types."""
    user = SessionUser(name="MultiModel", email="multi@example.com")
    post = SessionPost(title="Test Post", content="Content here")

    session.add(user)
    session.add(post)
    session.commit()

    assert user.id is not None
    assert post.id is not None


def test_session_update_tracking(session, db):
    """Test that session tracks updates to existing objects."""
    # Create and save a user
    user = SessionUser(name="Original", email="original@example.com")
    session.add(user)
    session.commit()

    user_id = user.id

    # Modify the user
    user.name = "Updated"
    session.add(user)  # Mark as dirty
    session.commit()

    # Verify update
    retrieved = SessionUser.get(user_id)
    assert retrieved.name == "Updated"


def test_session_rollback_clears_tracking(session, db):
    """Test that rollback clears all tracking sets."""
    user = SessionUser(name="RollbackTest", email="rollback@example.com")
    session.add(user)

    assert len(session._new) == 1

    session.rollback()

    assert len(session._new) == 0
    assert len(session._dirty) == 0
    assert len(session._deleted) == 0


def test_session_delete_without_id_warning(session, db):
    """Test that deleting object without ID doesn't crash."""
    user = SessionUser(name="NoId", email="noid@example.com")
    # Don't save, so no ID

    session.delete(user)
    session.commit()  # Should not crash


def test_session_commit_without_active_raises(session, db):
    """Test that commit on inactive session raises error."""
    session.close()

    with pytest.raises(RuntimeError, match="session is not active"):
        session.commit()


def test_session_nested_context_managers(db):
    """Test behavior with nested sessions (current implementation uses class variable)."""
    with Session(db) as session1:
        user1 = SessionUser(name="Session1", email="session1@example.com")
        session1.add(user1)

        # Inner session would override _current_session
        with Session(db) as session2:
            user2 = SessionUser(name="Session2", email="session2@example.com")
            session2.add(user2)
            assert Session.get_current() is session2

        # After inner session closes
        assert Session.get_current() is None  # session1 already exited


def test_session_add_same_instance_twice(session, db):
    """Test adding same instance twice doesn't cause issues."""
    user = SessionUser(name="Duplicate", email="dup@example.com")

    session.add(user)
    session.add(user)  # Add again

    session.commit()

    # Should only create one record
    assert user.id is not None
