import pytest
import sqlalchemy as sa

from sqlalchemy_utils.observer import observes


@pytest.mark.usefixtures('postgresql_dsn')
class TestObservesForColumn(object):

    @pytest.fixture
    def Product(self, Base):
        class Product(Base):
            __tablename__ = 'product'
            id = sa.Column(sa.Integer, primary_key=True)
            price = sa.Column(sa.Integer)

            @observes('price')
            def product_price_observer(self, price):
                self.price = price * 2
        return Product

    @pytest.fixture
    def init_models(self, Product):
        pass

    def test_simple_insert(self, session, Product):
        product = Product(price=100)
        session.add(product)
        session.flush()
        assert product.price == 200


@pytest.mark.usefixtures('postgresql_dsn')
class TestObservesForColumnWithoutActualChanges(object):

    @pytest.fixture
    def Product(self, Base):
        class Product(Base):
            __tablename__ = 'product'
            id = sa.Column(sa.Integer, primary_key=True)
            price = sa.Column(sa.Integer)

            @observes('price')
            def product_price_observer(self, price):
                raise Exception('Trying to change price')
        return Product

    @pytest.fixture
    def init_models(self, Product):
        pass

    def test_only_notifies_observer_on_actual_changes(self, session, Product):
        product = Product()
        session.add(product)
        session.flush()

        with pytest.raises(Exception) as e:
            product.price = 500
            session.commit()
        assert str(e.value) == 'Trying to change price'


@pytest.mark.usefixtures('postgresql_dsn')
class TestObservesForMultipleColumns(object):

    @pytest.fixture
    def Order(self, Base):
        class Order(Base):
            __tablename__ = 'order'
            id = sa.Column(sa.Integer, primary_key=True)
            unit_price = sa.Column(sa.Integer)
            amount = sa.Column(sa.Integer)
            total_price = sa.Column(sa.Integer)

            @observes('amount', 'unit_price')
            def total_price_observer(self, amount, unit_price):
                self.total_price = amount * unit_price
        return Order

    @pytest.fixture
    def init_models(self, Order):
        pass

    def test_only_notifies_observer_on_actual_changes(self, session, Order):
        order = Order()
        order.amount = 2
        order.unit_price = 10
        session.add(order)
        session.flush()

        order.amount = 1
        session.flush()
        assert order.total_price == 10

        order.unit_price = 100
        session.flush()
        assert order.total_price == 100


@pytest.mark.usefixtures('postgresql_dsn')
class TestObservesForMultipleColumnsFiresOnlyOnce(object):

    @pytest.fixture
    def Order(self, Base):
        class Order(Base):
            __tablename__ = 'order'
            id = sa.Column(sa.Integer, primary_key=True)
            unit_price = sa.Column(sa.Integer)
            amount = sa.Column(sa.Integer)

            @observes('amount', 'unit_price')
            def total_price_observer(self, amount, unit_price):
                self.call_count = self.call_count + 1
        return Order

    @pytest.fixture
    def init_models(self, Order):
        pass

    def test_only_notifies_observer_on_actual_changes(self, session, Order):
        order = Order()
        order.amount = 2
        order.unit_price = 10
        order.call_count = 0
        session.add(order)
        session.flush()
        assert order.call_count == 1

        order.amount = 1
        order.unit_price = 100
        session.flush()
        assert order.call_count == 2
