# -*- coding:utf-8 -*-
from __future__ import (
    absolute_import, division, print_function, unicode_literals
)

import json
from datetime import date, datetime, time
from unittest import SkipTest, mock

import pytest
from django.core import serializers
from django.db import connection, connections
from django.db.migrations.writer import MigrationWriter
from django.db.models import CharField, Transform
from django.test import TestCase
from django.utils import six

from django_mysql.models import DynamicField
from testapp.models import DynamicModel, TemporaryModel
from testapp.utils import requiresPython2


class DynColTestCase(TestCase):

    @classmethod
    def setUpClass(cls):
        if not (
            connection.is_mariadb and
            connection.mysql_version >= (10, 0, 1)
        ):
            raise SkipTest("Dynamic Columns require MariaDB 10.0.1+")
        super(DynColTestCase, cls).setUpClass()


class TestSaveLoad(DynColTestCase):

    def test_save_and_mutations(self):
        s = DynamicModel.objects.create()
        assert s.attrs == {}

        s = DynamicModel.objects.get()
        assert s.attrs == {}

        s.attrs['key'] = 'value!'
        s.attrs['2key'] = 23
        s.save()
        s = DynamicModel.objects.get()
        assert s.attrs == {'key': 'value!', '2key': 23}

        del s.attrs['key']
        s.save()
        s = DynamicModel.objects.get()
        assert s.attrs == {'2key': 23}

        del s.attrs['2key']
        s.save()
        s = DynamicModel.objects.get()
        assert s.attrs == {}

    def test_create(self):
        DynamicModel.objects.create(attrs={
            'a': 'value'
        })
        s = DynamicModel.objects.get()
        assert s.attrs == {'a': 'value'}

    def test_create_succeeds_specced_field(self):
        DynamicModel.objects.create(attrs={'inty': 1})
        s = DynamicModel.objects.get()
        assert s.attrs == {'inty': 1}

    def test_create_fails_bad_value(self):
        with pytest.raises(TypeError):
            DynamicModel.objects.create(attrs={'inty': 1.0})

    def test_bulk_create(self):
        DynamicModel.objects.bulk_create([
            DynamicModel(attrs={'a': 'value'}),
            DynamicModel(attrs={'b': 'value2'}),
        ])
        dm1, dm2 = DynamicModel.objects.all().order_by('id')
        assert dm1.attrs == {'a': 'value'}
        assert dm2.attrs == {'b': 'value2'}


class SpecTests(DynColTestCase):

    def test_spec_dict_type(self):
        DynamicField.validate_spec(
            {'a': dict},
            {'a': {'this': 'that'}}
        )  # no errors

    def test_illegal_int(self):
        m = DynamicModel(attrs={'inty': 1.0})
        with pytest.raises(TypeError) as excinfo:
            m.save()
        assert "Key 'inty' should be of type int" in str(excinfo.value)

    def test_illegal_nested(self):
        m = DynamicModel(attrs={'nesty': {'level2': 1}})
        with pytest.raises(TypeError) as excinfo:
            m.save()
        assert "Key 'nesty.level2' should be of type " in str(excinfo.value)

    def test_illegal_nested_type(self):
        m = DynamicModel(attrs={'nesty': []})
        with pytest.raises(TypeError) as excinfo:
            m.save()
        assert "Key 'nesty' should be of type dict" in str(excinfo.value)

    @requiresPython2
    def test_long_equivalent_to_int(self):
        from __builtin__ import long  # make source lintable on Python 3
        DynamicField.validate_spec({'a': int}, {'a': long(9001)})  # no errors

    @requiresPython2
    def test_int_equivalent_to_long(self):
        from __builtin__ import long  # make source lintable on Python 3
        DynamicField.validate_spec({'a': long}, {'a': int(9001)})  # no errors


class DumbTransform(Transform):
    """
    Used to test existing transform behaviour - by default in Django there are
    no transforms on BinaryField.
    Really dumb, returns the string 'dumb' always
    """
    lookup_name = 'dumb'
    output_field = CharField()

    def as_sql(self, compiler, connection):
        lhs, params = compiler.compile(self.lhs)
        return "%s", ['dumb']

DynamicField.register_lookup(DumbTransform)


class QueryTests(DynColTestCase):

    def setUp(self):
        super(QueryTests, self).setUp()
        self.objs = [
            DynamicModel(attrs={'a': 'b'}),
            DynamicModel(attrs={'a': 'b', 'c': 'd'}),
            DynamicModel(attrs={'c': 'd'}),
            DynamicModel(attrs={}),
            DynamicModel(attrs={
                'datetimey': datetime(2001, 1, 4, 14, 15, 16),
                'datey': date(2001, 1, 4),
                'floaty': 128.5,
                'inty': 9001,
                'stry': "strvalue",
                'str_underscorey': "strvalue2",
                'timey': time(14, 15, 16),
                'nesty': {
                    'level2': 'chirp'
                }
            }),
        ]
        DynamicModel.objects.bulk_create(self.objs)
        self.objs = list(DynamicModel.objects.all().order_by('id'))

    def test_equal(self):
        assert (
            list(DynamicModel.objects.filter(attrs={'a': 'b'})) ==
            self.objs[:1]
        )

    def test_exact(self):
        assert (
            list(DynamicModel.objects.filter(attrs__exact={'a': 'b'})) ==
            self.objs[:1]
        )

    def test_preexisting_transforms_work_fine(self):
        assert (
            list(DynamicModel.objects.filter(attrs__dumb='notdumb')) ==
            []
        )

    def test_has_key(self):
        assert (
            list(DynamicModel.objects.filter(attrs__has_key='c')) ==
            self.objs[1:3]
        )

    def test_key_transform_datey(self):
        assert (
            list(DynamicModel.objects.filter(attrs__datey=date(2001, 1, 4))) ==
            [self.objs[4]]
        )

    def test_key_transform_datey_DATE(self):
        assert (
            list(DynamicModel.objects.filter(
                attrs__datey_DATE=date(2001, 1, 4)
            )) ==
            [self.objs[4]]
        )

    def test_key_transform_datetimey(self):
        assert (
            list(DynamicModel.objects.filter(
                attrs__datetimey=datetime(2001, 1, 4, 14, 15, 16)
            )) ==
            [self.objs[4]]
        )

    def test_key_transform_datetimey__year(self):
        assert (
            list(DynamicModel.objects.filter(attrs__datetimey__year=2001)) ==
            [self.objs[4]]
        )

    def test_key_transform_datetimey_DATETIME(self):
        assert (
            list(DynamicModel.objects.filter(
                attrs__datetimey_DATETIME=datetime(2001, 1, 4, 14, 15, 16)
            )) ==
            [self.objs[4]]
        )

    def test_key_transform_floaty(self):
        assert (
            list(DynamicModel.objects.filter(attrs
__floaty__gte=128.0)) ==
            [self.objs[4]]
        )

    def test_key_transform_floaty_DOUBLE(self):
        assert (
            list(DynamicModel.objects.filter(attrs__floaty_DOUBLE=128.5)) ==
            [self.objs[4]]
        )

    def test_key_transform_inty(self):
        assert (
            list(DynamicModel.objects.filter(attrs__inty=9001)) ==
            [self.objs[4]]
        )

    def test_key_transform_inty_INTEGER(self):
        assert (
            list(DynamicModel.objec
ts.filter(attrs__inty_INTEGER=9001)) ==
            [self.objs[4]]
        )

    def test_key_transform_inty_no_results(self):
        assert (
            list(DynamicModel.objects.filter(attrs__inty=12991)) ==
            []
        )

    def test_key_transform_inty_in_subquery(self):
        assert (
            list(DynamicModel.objects.filter(
                id__in=DynamicModel.objects.filter(attrs__inty=9001),
            )) ==
            [self.objs[4]]
        )

    def test_key_transform_miss_CHAR_isnull(self):
        assert (
            list(DynamicModel.objects.filter(attrs__miss_CHAR__isnull=True)) ==
            self.objs
        )

    def test_key_transform_stry(self):
        assert (
            list(DynamicModel.objects.filter(attrs__stry="strvalue")) ==
            [self.objs[4]]
        )

    def test_key_transform_stry_CHAR(self):
        a