from datetime import datetime, date
from decimal import Decimal
from json import loads
try:
    from sqlalchemy import orm, schema, types
    from sqlalchemy.engine import create_engine
except ImportError:
    create_engine = None  # noqa

from webtest import TestApp
from webob.multidict import MultiDict

from pecan.jsonify import jsonify, encode, ResultProxy, RowProxy
from pecan import Pecan, expose
from pecan.tests import PecanTestCase


def make_person():
    class Person(object):
        def __init__(self, first_name, last_name):
            self.first_name = first_name
            self.last_name = last_name

        @property
        def name(self):
            return '%s %s' % (self.first_name, self.last_name)
    return Person


def test_simple_rule():
    Person = make_person()

    # create a Person instance
    p = Person('Jonathan', 'LaCour')

    # register a generic JSON rule
    @jsonify.when_type(Person)
    def jsonify_person(obj):
        return dict(
            name=obj.name
        )

    # encode the object using our new rule
    result = loads(encode(p))
    assert result['name'] == 'Jonathan LaCour'
    assert len(result) == 1


class TestJsonify(PecanTestCase):

    def test_simple_jsonify(self):
        Person = make_person()

        # register a generic JSON rule
        @jsonify.when_type(Person)
        def jsonify_person(obj):
            return dict(
                name=obj.name
            )

        class RootController(object):
            @expose('json')
            def index(self):
                # create a Person instance
                p = Person('Jonathan', 'LaCour')
                return p

        app = TestApp(Pecan(RootController()))

        r = app.get('/')
        assert r.status_int == 200
        assert loads(r.body.decode()) == {'name': 'Jonathan LaCour'}


class TestJsonifyGenericEncoder(PecanTestCase):
    def test_json_callable(self):
        class JsonCallable(object):
            def __init__(self, arg):
                self.arg = arg

            def __json__(self):
                return {"arg": self.arg}

        result = encode(JsonCallable('foo'))
        assert loads(result) == {'arg': 'foo'}

    def test_datetime(self):
        today = date.today()
        now = datetime.now()

        result = encode(today)
        assert loads(result) == str(today)

        result = encode(now)
        assert loads(result) == str(now)

    def test_decimal(self):
        # XXX Testing for float match which is inexact

        d = Decimal('1.1')
        result = encode(d)
        assert loads(result) == float(d)

    def test_multidict(self):
        md = MultiDict()
        md.add('arg', 'foo')
        md.add('arg', 'bar')
        result = encode(md)
        assert loads(result) == {'arg': ['foo', 'bar']}

    def test_fallback_to_builtin_encoder(self):
        class Foo(object):
            pass

        self.assertRaises(TypeError, encode, Foo())


class TestJsonifySQLAlchemyGenericEncoder(PecanTestCase):

    def setUp(self):
        super(TestJsonifySQLAlchemyGenericEncoder, self).setUp()
        if not create_engine:
            self.create_fake_proxies()
        else:
            self.create_sa_proxies()

    def create_fake_proxies(self):

        # create a fake SA object
        class FakeSAObject(object):
            def __init__(self):
                self._sa_class_manager = object()
                self._sa_instance_state = 'awesome'
                self.id = 1
                self.first_name = 'Jonathan'
                self.last_name = 'LaCour'

        # create a fake result proxy
        class FakeResultProxy(ResultProxy):
            def __init__(self):
                self.rowcount = -1
                self.rows = []

            def __iter__(self):
                return iter(self.rows)

            def append(self, row):
                self.rows.append(row)

        # create a fake row proxy
        class FakeRowProxy(RowProxy):
            def __init__(self, arg=None):
                self.row = dict(arg)

            def __getitem__(self, key):
                return self.row.__getitem__(key)

            def keys(self):
                return self.row.keys()

        # get the SA objects
        self.sa_object = FakeSAObject()
        self.result_proxy = FakeResultProxy()
        self.result_proxy.append(
            FakeRowProxy([
                ('id', 1),
                ('first_name', 'Jonathan'),
                ('last_name', 'LaCour')
            ])
        )
        self.result_proxy.append(
            FakeRowProxy([
                ('id', 2), ('first_name', 'Yoann'), ('last_name', 'Roman')
            ]))
        self.row_proxy = FakeRowProxy([
            ('id', 1), ('first_name', 'Jonathan'), ('last_name', 'LaCour')
        ])

    def create_sa_proxies(self):

        # create the table and mapper
        metadata = schema.MetaData()
        user_table = schema.Table(
            'user',
            metadata,
            schema.Column('id', types.Integer, primary_key=True),
            schema.Column('first_name', types.Unicode(25)),
            schema.Column('last_name', types.Unicode(25))
        )

        class User(object):
            pass
        orm.mapper(User, user_table)

        # create the session
        engine = create_engine('sqlite:///:memory:')
        metadata.bind = engine
        metadata.create_all()
        session = orm.sessionmaker(bind=engine)()

        # add some dummy data
        user_table.insert().execute([
            {'first_name': 'Jonathan', 'last_name': 'LaCour'},
            {'first_name': 'Yoann', 'last_name': 'Roman'}
        ])

        # get the SA objects
        self.sa_object = session.query(User).first()
        select = user_table.select()
        self.result_proxy = select.execute()
        self.row_proxy = select.execute().fetchone()

    def test_sa_object(self):
        result = encode(self.sa_object)
        assert loads(result) == {
            'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'
        }

    def test_result_proxy(self):
        result = encode(self.result_proxy)
        assert loads(result) == {'count': 2, 'rows': [
            {'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'},
            {'id': 2, 'first_name': 'Yoann', 'last_name': 'Roman'}
        ]}

    def test_row_proxy(self):
        result = encode(self.row_proxy)
        assert loads(result) == {
            'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'
        }
