import pytest


from hypr.models import MemoryModel
from hypr.exc import ModelSearchException, ModelFilterException, \
    ModelConflictException


class Model(MemoryModel):

    __key__ = 'id'


class SearchableModel(Model):

    __search__ = 'name'


@pytest.fixture(scope='class')
def empty_db(request):
    Model.reset()

    def finalizer():
        Model.reset()
    request.addfinalizer(finalizer)


@pytest.fixture(scope='class')
def filled_db(request, empty_db):
    for i in range(1000):
        user = Model(id=i+1, name='user{}'.format(i), group=i % 10)
        user.save(commit=False)
    Model.commit()


@pytest.mark.usefixtures('empty_db')
class TestMemoryModel0:
    # All those tests are ran against an empty db

    def test_one_not_found(self):
        assert Model.one(1) is None

    def test_get_empty(self):
        assert Model.get() == []

    def test_create_instance(self):
        model = Model(id=1, name='test', group='group')
        model.save()
        assert Model.one(model.id)


@pytest.mark.usefixtures('filled_db')
class TestMemoryModel1:
    # All those tests are ran against a db filled with 1000 records.

    def test_one(self):
        assert Model.one(1)

    def test_count(self):
        assert Model.count() == 1000

    def test_get(self):
        assert Model.get()

    def test_get_filter(self):
        rv = Model.get(group=0)
        assert len(rv) == 100

    def test_get_filter_empty(self):
        assert not Model.get(group='fail')

    def test_get_limit(self):
        rv = Model.get(_limit=10)
        assert len(rv) == 10

    def test_get_limit_offset(self):
        rv0 = Model.get(_limit=10)
        rv1 = Model.get(_limit=10, _offset=1)
        assert len(rv0) == len(rv1) == 10
        assert rv0 != rv1

    def test_get_filter_limit(self):
        rv = Model.get(group=0, _limit=500)
        assert len(rv) == 100

    def test_invalid_filter(self):
        with pytest.raises(ModelFilterException):
            Model.get(unknown='filter')

    def test_get_filter_multiple_terms(self):
        rv = Model.get(name=('user17', 'user483'))
        assert len(rv) == 2

    def test_get_multiple_filters(self):
        rv = Model.get(name=('user17', 'user483'), id=18)
        assert len(rv) == 1

    def test_delete_autocommit(self):
        Model.one(10).delete()
        assert not Model.one(10)

    def test_delete_manual_commit(self):
        Model.one(11).delete(commit=False)
        Model.commit()
        assert not Model.one(11)

    def test_delete_rollback(self):
        Model.one(12).delete(commit=False)
        Model.rollback()
        assert Model.one(12)

    def test_save_autocommit(self):
        target = Model.one(1)
        target.name = 'modified'
        target.save()
        assert Model.one(1).name == 'modified'

    def test_save_manual_commit(self):
        target = Model.one(2)
        target.name = 'modified'
        target.save(commit=False)
        target.commit()
        assert Model.one(2).name == 'modified'

    def test_save_rollback(self):
        target = Model.one(3)
        target.name = 'modified'
        target.save(commit=False)
        target.rollback()
        assert Model.one(3).name != 'modified'

    def test_conflict(self):
        with pytest.raises(ModelConflictException):
            target = Model.one(1)
            target.id = 3
            target.save()

    def test_search(self):
        with pytest.raises(ModelSearchException):
            Model.get(_search='fail')


@pytest.mark.usefixtures('empty_db')
class TestMemoryModel2:

    def test_method_names_reserved(self):
        with pytest.raises(ValueError):
            Model(id=1, name='test', group='group', save='fail')
