# -*- coding:utf-8 -*-
from __future__ import unicode_literals

import pytest
from django.core.management import call_command
from django.db import connection
from django.db.migrations.writer import MigrationWriter
from django.db.transaction import atomic
from django.db.utils import DataError
from django.test import TestCase, TransactionTestCase
from django.test.utils import override_settings
from django.utils import six

from django_mysql.models import SizedBinaryField, SizedTextField
from django_mysql.test.utils import override_mysql_variables
from django_mysql_tests.models import SizeFieldModel
from django_mysql_tests.utils import column_type

# Ensure we aren't just warned about the data truncation
forceDataError = override_mysql_variables(SQL_MODE='STRICT_TRANS_TABLES')


def migrate(name):
    call_command('migrate', 'django_mysql_tests', name, verbosity=0)


@forceDataError
class SizedBinaryFieldTests(TestCase):

    def test_binaryfield_checks(self):
        field = SizedBinaryField(size_class=5)
        field.set_attributes_from_name('field')
        errors = field.check()
        assert len(errors) == 1
        assert errors[0].id == 'django_mysql.E007'
        assert errors[0].msg == 'size_class must be 1, 2, 3, or 4'

    def test_binaryfield_default_length(self):
        # By default, SizedBinaryField should act like BinaryField
        field = SizedBinaryField()
        assert field.size_class == 4
        assert field.db_type(None) == 'longblob'

    @atomic
    def test_binary_1_max_length(self):
        # Okay
        m = SizeFieldModel(binary1=six.binary_type(1) * (2**8 - 1))
        m.save()

        # Bad - Data too long
        m = SizeFieldModel(binary1=six.binary_type(1) * (2**8))
        with pytest.raises(DataError) as excinfo:
            m.save()
        assert excinfo.value.args[0] == 1406


@forceDataError
class SizedBinaryFieldMigrationTests(TransactionTestCase):

    def test_deconstruct_size_class_4(self):
        field = SizedBinaryField(size_class=4)
        name, path, args, kwargs = field.deconstruct()
        new = SizedBinaryField(*args, **kwargs)
        assert new.size_class == field.size_class

    def test_deconstruct_size_class_2(self):
        field = SizedBinaryField(size_class=2)
        name, path, args, kwargs = field.deconstruct()
        new = SizedBinaryField(*args, **kwargs)
        assert new.size_class == field.size_class

    def test_makemigrations(self):
        field = SizedBinaryField(size_class=1)
        statement, imports = MigrationWriter.serialize(field)

        assert (
            statement ==
            "django_mysql.models.SizedBinaryField(size_class=1)"
        )

    def test_makemigrations_size_class_implicit(self):
        field = SizedBinaryField()
        statement, imports = MigrationWriter.serialize(field)

        assert (
            statement ==
            "django_mysql.models.SizedBinaryField(size_class=4)"
        )

    @override_settings(MIGRATION_MODULES={
        "django_mysql_tests": "django_mysql_tests.sizedbinaryfield_migrations",
    })
    def test_adding_field_with_default(self):
        table_name = 'django_mysql_tests_sizedbinaryaltermodel'
        table_names = connection.introspection.table_names

        with connection.cursor() as cursor:
            assert table_name not in table_names(cursor)

        migrate('0001_initial')
        with connection.cursor() as cursor:
            assert table_name in table_names(cursor)
            assert column_type(table_name, 'field') == 'longblob'

        migrate('0002_alter_field')
        with connection.cursor() as cursor:
            assert table_name in table_names(cursor)
            assert column_type(table_name, 'field') == 'blob'

        migrate('0001_initial')
        with connection.cursor() as cursor:
            assert table_name in table_names(cursor)
            assert column_type(table_name, 'field') == 'longblob'

        migrate('zero')
        with connection.cursor() as cursor:
            assert table_name not in table_names(cursor)


@forceDataError
class SizedTextFieldTests(TestCase):

    def test_check_max_length(self):
        field = SizedTextField(size_class=5)
        field.set_attributes_from_name('field')
        errors = field.check()
        assert len(errors) == 1
        assert errors[0].id == 'django_mysql.E008'
        assert errors[0].msg == 'size_class must be 1, 2, 3, or 4'

    def test_textfield_default_length(self):
        # By default, SizedTextField should act like TextField
        field = SizedTextField()
        assert field.size_class == 4
        assert field.db_type(None) == 'longtext'

    @atomic
    def test_tinytext_max_length(self):
        # Okay
        m = SizeFieldModel(text1='a' * (2**8 - 1))
        m.save()

        # Bad - Data too long
        m = SizeFieldModel(text1='a' * (2**8))
        with pytest.raises(DataError) as excinfo:
            m.save()
        assert excinfo.value.args[0] == 1406


@forceDataError
class SizedTextFieldMigrationTests(TransactionTestCase):

    def test_deconstruct_size_class_4(self):
        field = SizedTextField(size_class=4)
        name, path, args, kwargs = field.deconstruct()
        new = SizedTextField(*args, **kwargs)
        assert new.size_class == field.size_class

    def test_deconstruct_size_class_2(self):
        field = SizedTextField(size_class=2)
        name, path, args, kwargs = field.deconstruct()
        new = SizedTextField(*args, **kwargs)
        assert new.size_class == field.size_class

    def test_makemigrations(self):
        field = SizedTextField(size_class=1)
        statement, imports = MigrationWriter.serialize(field)

        assert (
            statement ==
            "django_mysql.models.SizedTextField(size_class=1)"
        )

    def test_makemigrations_size_class_implicit(self):
        field = SizedTextField()
        statement, imports = MigrationWriter.serialize(field)

        assert (
            statement ==
            "django_mysql.models.SizedTextField(size_class=4)"
        )

    @override_settings(MIGRATION_MODULES={
        "django_mysql_tests": "django_mysql_tests.sizedtextfield_migrations",
    })
    def test_adding_field_with_default(self):
        table_name = 'django_mysql_tests_sizedtextaltermodel'
        table_names = connection.introspection.table_names

        with connection.cursor() as cursor:
            assert table_name not in table_names(cursor)

        migrate('0001_initial')
        with connection.cursor() as cursor:
            assert table_name in table_names(cursor)
            assert column_type(table_name, 'field') == 'mediumtext'

        migrate('0002_alter_field')
        with connection.cursor() as cursor:
            assert table_name in table_names(cursor)
            assert column_type(table_name, 'field') == 'tinytext'

        migrate('0001_initial')
        with connection.cursor() as cursor:
            assert table_name in table_names(cursor)
            assert column_type(table_name, 'field') == 'mediumtext'

        migrate('zero')
        with connection.cursor() as cursor:
            assert table_name not in table_names(cursor)
