from nose.tools import eq_, ok_
from nose.plugins.skip import SkipTest

# Skip test on PY3
from flask.ext.admin._compat import PY2, as_unicode
if not PY2:
    raise SkipTest('Peewee is not Python 3 compatible')

import peewee

from wtforms import fields

from flask.ext.admin import form
from flask.ext.admin._compat import iteritems
from flask.ext.admin.contrib.peewee import ModelView

from . import setup

from datetime import datetime, time, date


class CustomModelView(ModelView):
    def __init__(self, model,
                 name=None, category=None, endpoint=None, url=None,
                 **kwargs):
        for k, v in iteritems(kwargs):
            setattr(self, k, v)

        super(CustomModelView, self).__init__(model,
                                              name, category,
                                              endpoint, url)


def create_models(db):
    class BaseModel(peewee.Model):
        class Meta:
            database = db

    class Model1(BaseModel):
        def __init__(self, test1=None, test2=None, test3=None, test4=None,
                     date_field=None, timeonly_field=None, 
                     datetime_field=None):
            super(Model1, self).__init__()

            self.test1 = test1
            self.test2 = test2
            self.test3 = test3
            self.test4 = test4
            self.date_field = date_field
            self.timeonly_field = timeonly_field
            self.datetime_field = datetime_field

        test1 = peewee.CharField(max_length=20, null=True)
        test2 = peewee.CharField(max_length=20, null=True)
        test3 = peewee.TextField(null=True)
        test4 = peewee.TextField(null=True)
        date_field = peewee.DateField(null=True)
        timeonly_field = peewee.TimeField(null=True)
        datetime_field = peewee.DateTimeField(null=True)

        def __str__(self):
            # "or ''" fixes error when loading choices for relation field:
            # TypeError: coercing to Unicode: need string or buffer, NoneType found
            return self.test1 or ''

    class Model2(BaseModel):
        def __init__(self, char_field=None, int_field=None, float_field=None,
                     bool_field=0):
            super(Model2, self).__init__()

            self.char_field = char_field
            self.int_field = int_field
            self.float_field = float_field
            self.bool_field = bool_field

        char_field = peewee.CharField(max_length=20)
        int_field = peewee.IntegerField(null=True)
        float_field = peewee.FloatField(null=True)
        bool_field = peewee.BooleanField()

        # Relation
        model1 = peewee.ForeignKeyField(Model1, null=True)

    Model1.create_table()
    Model2.create_table()

    return Model1, Model2


def fill_db(Model1, Model2):
    Model1('test1_val_1', 'test2_val_1').save()
    Model1('test1_val_2', 'test2_val_2').save()
    Model1('test1_val_3', 'test2_val_3').save()
    Model1('test1_val_4', 'test2_val_4').save()
    Model1(None, 'empty_obj').save()

    Model2('char_field_val_1', None, None).save()
    Model2('char_field_val_2', None, None).save()
    Model2('char_field_val_3', 5000, 25.9).save()
    Model2('char_field_val_4', 9000, 75.5).save()

    Model1('date_obj1', date_field=date(2014,11,17)).save()
    Model1('date_obj2', date_field=date(2013,10,16)).save()
    Model1('timeonly_obj1', timeonly_field=time(11,10,9)).save()
    Model1('timeonly_obj2', timeonly_field=time(10,9,8)).save()
    Model1('datetime_obj1', datetime_field=datetime(2014,4,3,1,9,0)).save()
    Model1('datetime_obj2', datetime_field=datetime(2013,3,2,0,8,0)).save()


def test_model():
    app, db, admin = setup()
    Model1, Model2 = create_models(db)

    view = CustomModelView(Model1)
    admin.add_view(view)

    eq_(view.model, Model1)
    eq_(view.name, 'Model1')
    eq_(view.endpoint, 'model1')

    eq_(view._primary_key, 'id')

    ok_('test1' in view._sortable_columns)
    ok_('test2' in view._sortable_columns)
    ok_('test3' in view._sortable_columns)
    ok_('test4' in view._sortable_columns)

    ok_(view._create_form_class is not None)
    ok_(view._edit_form_class is not None)
    eq_(view._search_supported, False)
    eq_(view._filters, None)

    # Verify form
    eq_(view._create_form_class.test1.field_class, fields.StringField)
    eq_(view._create_form_class.test2.field_class, fields.StringField)
    eq_(view._create_form_class.test3.field_class, fields.TextAreaField)
    eq_(view._create_form_class.test4.field_class, fields.TextAreaField)

    # Make some test clients
    client = app.test_client()

    rv = client.get('/admin/model1/')
    eq_(rv.status_code, 200)

    rv = client.get('/admin/model1/new/')
    eq_(rv.status_code, 200)

    rv = client.post('/admin/model1/new/',
                     data=dict(test1='test1large', test2='test2'))
    eq_(rv.status_code, 302)

    model = Model1.select().get()
    eq_(model.test1, 'test1large')
    eq_(model.test2, 'test2')
    ok_(model.test3 is None or model.test3 == '')
    ok_(model.test4 is None or model.test4 == '')

    rv = client.get('/admin/model1/')
    eq_(rv.status_code, 200)
    ok_('test1large' in rv.data)

    url = '/admin/model1/edit/?id=%s' % model.id
    rv = client.get(url)
    eq_(rv.status_code, 200)

    rv = client.post(url,
                     data=dict(test1='test1small', test2='test2large'))
    eq_(rv.status_code, 302)

    model = Model1.select().get()
    eq_(model.test1, 'test1small')
    eq_(model.test2, 'test2large')
    ok_(model.test3 is None or model.test3 == '')
    ok_(model.test4 is None or model.test4 == '')

    url = '/admin/model1/delete/?id=%s' % model.id
    rv = client.post(url)
    eq_(rv.status_code, 302)
    eq_(Model1.select().count(), 0)


def test_column_editable_list():
    app, db, admin = setup()

    Model1, Model2 = create_models(db)

    view = CustomModelView(Model1,
                           column_editable_list=[
                               'test1', 'enum_field'])
    admin.add_view(view)

    fill_db(Model1, Model2)

    client = app.test_client()

    # Test in-line edit field rendering
    rv = client.get('/admin/model1/')
    data = rv.data.decode('utf-8')
    ok_('data-role="x-editable"' in data)

    # Form - Test basic in-line edit functionality
    rv = client.post('/admin/model1/ajax/update/', data={
        'test1-1': 'change-success-1',
    })
    data = rv.data.decode('utf-8')
    ok_('Record was successfully saved.' == data)

    # ensure the value has changed
    rv = client.get('/admin/model1/')
    data = rv.data.decode('utf-8')
    ok_('change-success-1' in data)

    # Test validation error
    rv = client.post('/admin/model1/ajax/update/', data={
        'enum_field-1': 'problematic-input',
    })
    eq_(rv.status_code, 500)

    # Test invalid primary key
    rv = client.post('/admin/model1/ajax/update/', data={
        'test1-1000': 'problematic-input',
    })
    data = rv.data.decode('utf-8')
    eq_(rv.status_code, 500)

    # Test editing column not in column_editable_list
    rv = client.post('/admin/model1/ajax/update/', data={
        'test2-1': 'problematic-input',
    })
    data = rv.data.decode('utf-8')
    eq_(rv.status_code, 500)

    # Test in-line editing for relations
    view = CustomModelView(Model2,
                           column_editable_list=[
                               'model1'])
    admin.add_view(view)

    rv = client.post('/admin/model2/ajax/update/', data={
        'model1-1': '3',
    })
    data = rv.data.decode('utf-8')
    ok_('Record was successfully saved.' == data)

    # confirm the value has changed
    rv = client.get('/admin/model2/')
    data = rv.data.decode('utf-8')
    ok_('test1_val_3' in data)


def test_column_filters():
    app, db, admin = setup()
    
    Model1, Model2 = create_models(db)
    
    fill_db(Model1, Model2)

    # Test string filter
    view = CustomModelView(Model1, column_filters=['test1'])
    admin.add_view(view)
    
    eq_(len(view._filters), 7)

    eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']],
        [
            (0, 'equals'),
            (1, 'not equal'),
            (2, 'contains'),
            (3, 'not contains'),
            (4, 'empty'),
            (5, 'in list'),
            (6, 'not in list'),
        ])
        
    # Make some test clients
    client = app.test_client()
    
    # string - equals
    rv = client.get('/admin/model1/?flt0_0=test1_val_1')
    eq_(rv.status_code, 200)
    data = rv.data.decode('utf-8')
    ok_('test2_val_1' in data)
    ok_('test1_val_2' not in data)
    
    # string - not equal
    rv = client.get('/admin/model1/?flt0_1=test1_val_1')
    eq_(rv.status_code, 200)
    data = rv.data.decode('utf-8')
    ok_('test2_val_1' not in data)
    ok_('test1_val_2' in data)
    
    # string - contains
    rv = client.get('/admin/model1/?flt0_2=test1_val_1')
    eq_(rv.status_code, 200)
    data = rv.data.decode('utf-8')
    ok_('test2_val_1' in data)
    ok_('test1_val_2' not in data)
    
    # string - not contains
    rv = client.get('/admin/model1/?flt0_3=test1_val_1')
    eq_(rv.status_code, 200)
    data = rv.data.decode('utf-8')
    ok_('test2_val_1' not in data)
    ok_('test1_val_2' in data)
    
    # string - empty
    rv = client.get('/admin/model1/?flt0_4=1')
    eq_(rv.status_code, 200)
    data = rv.data.decode('utf-8')
    ok_('empty_obj' in data)
    ok_('test1_val_1' not in data)
    ok_('test1_val_2' not in data)
    
    # string - not empty
    rv = client.get('/admin/model1/?flt0_4=0')
    eq_(rv.status_code, 200)
    data = rv.data.decode('utf-8')
    ok_('empty_obj' not in data)
    ok_('test1_val_1' in data)
    ok_('test1_val_2' in data)
    
    # string - in list
    rv = client.get('/admin/model1/?flt0_5=test1_val_1%2Ctest1_val_2')
    eq_(rv.status_code, 200)
    data = rv.data.decode('utf-8')
    ok_('test2_val_1' in data)
    ok_('test2_val_2' in data)
    ok_('test1_val_3' not in data)
    ok_('test1_val_4' not in data)
    
    # string - not in list
    rv = client.get('/admin/model1/?flt0_6=test1_val_1%2Ctest1_val_2')
    eq_(rv.status_code, 200)
    data = rv.data.decode('utf-8')
    ok_('test2_val_1' not in data)
    ok_('test2_val_2' not in data)
    ok_('test1_val_3' in data)
    ok_('test1_val_4' in data)
    
    # Test int filter
    view = CustomModelView(Model2, column_filters=['int_field'])
    admin.add_view(view)
    
    eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Int Field']],
        [
            (0, 'equals'),
            (1, 'not equal'),
            (2, 'greater than'),