# -*- coding: utf-8 -*-
# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import unittest

from sql import Literal

from trytond import backend
from trytond.model.exceptions import (
    ForbiddenCharValidationError, RequiredValidationError, SizeValidationError)
from trytond.pool import Pool
from trytond.tests.test_tryton import (
    ExtensionTestCase, TestCase, activate_module, with_transaction)
from trytond.transaction import Transaction


class CommonTestCaseMixin:

    @with_transaction()
    def test_create(self):
        "Test create char"
        Char = self.Char()

        char, char_none = Char.create([{
                    'char': "Test",
                    }, {
                    'char': None,
                    }])

        self.assertEqual(char.char, "Test")
        self.assertEqual(char_none.char, None)

    @with_transaction()
    def test_create_unicode(self):
        "Test create unicode"
        Char = self.Char()

        char, = Char.create([{
                    'char': "é",
                    }])

        self.assertEqual(char.char, "é")

    @with_transaction()
    def test_search_equals(self):
        "Test search char equals"
        Char = self.Char()
        char, = Char.create([{
                    'char': "Foo",
                    }])

        chars_foo = Char.search([
                ('char', '=', "Foo"),
                ])
        chars_bar = Char.search([
                ('char', '=', "Bar"),
                ])

        self.assertListEqual(chars_foo, [char])
        self.assertListEqual(chars_bar, [])

    @with_transaction()
    def test_search_equals_none(self):
        "Test search char equals None"
        Char = self.Char()
        char, = Char.create([{
                    'char': None,
                    }])

        chars = Char.search([
                ('char', '=', None),
                ])

        self.assertListEqual(chars, [char])

    @with_transaction()
    def test_search_none_equals_empty(self):
        "Test search char None equals empty"
        Char = self.Char()
        char, = Char.create([{
                    'char': None,
                    }])

        chars = Char.search([
                ('char', '=', ''),
                ])

        self.assertListEqual(chars, [char])

    @with_transaction()
    def test_search_empty_equals_none(self):
        "Test search char empty equals none"
        Char = self.Char()
        char, = Char.create([{
                    'char': '',
                    }])

        chars = Char.search([
                ('char', '=', None),
                ])

        self.assertListEqual(chars, [char])

    @with_transaction()
    def test_search_equals_unicode(self):
        "Test search char equals unicode"
        Char = self.Char()
        char, = Char.create([{
                    'char': "é",
                    }])

        chars = Char.search([
                ('char', '=', "é"),
                ])

        self.assertListEqual(chars, [char])

    @with_transaction()
    def test_search_equals_non_unicode(self):
        "Test search char equals non unicode"
        Char = self.Char()
        char, = Char.create([{
                    'char': "é",
                    }])

        chars = Char.search([
                ('char', '=', "é"),
                ])

        self.assertListEqual(chars, [char])

    @with_transaction()
    def test_search_non_equals(self):
        "Test search char non equals"
        Char = self.Char()
        char, = Char.create([{
                    'char': "Foo",
                    }])

        chars_foo = Char.search([
                ('char', '!=', "Foo"),
                ])
        chars_bar = Char.search([
                ('char', '!=', "Bar"),
                ])

        self.assertListEqual(chars_foo, [])
        self.assertListEqual(chars_bar, [char])

    @with_transaction()
    def test_search_non_equals_none(self):
        "Test search char non equals None"
        Char = self.Char()
        char, = Char.create([{
                    'char': None,
                    }])

        chars = Char.search([
                ('char', '!=', None),
                ])

        self.assertListEqual(chars, [])

    @with_transaction()
    def test_search_in(self):
        "Test search char in"
        Char = self.Char()
        char, = Char.create([{
                    'char': "Foo",
                    }])

        chars_foo = Char.search([
                ('char', 'in', ["Foo"]),
                ])
        chars_bar = Char.search([
                ('char', 'in', ["Bar"]),
                ])
        chars_empty = Char.search([
                ('char', 'in', []),
                ])

        self.assertListEqual(chars_foo, [char])
        self.assertListEqual(chars_bar, [])
        self.assertListEqual(chars_empty, [])

    @with_transaction()
    def test_search_in_none(self):
        "Test search char in [None]"
        Char = self.Char()
        char, = Char.create([{
                    'char': None,
                    }])

        chars = Char.search([
                ('char', 'in', [None]),
                ])

        self.assertListEqual(chars, [char])

    @with_transaction()
    def test_search_not_in(self):
        "Test search char not in"
        Char = self.Char()
        char, = Char.create([{
                    'char': "Foo",
                    }])

        chars_foo = Char.search([
                ('char', 'not in', ["Foo"]),
                ])
        chars_bar = Char.search([
                ('char', 'not in', ["Bar"]),
                ])
        chars_empty = Char.search([
                ('char', 'not in', []),
                ])

        self.assertListEqual(chars_foo, [])
        self.assertListEqual(chars_bar, [char])
        self.assertListEqual(chars_empty, [char])

    @with_transaction()
    def test_search_not_in_none(self):
        "Test search char not in [None]"
        Char = self.Char()
        char, = Char.create([{
                    'char': None,
                    }])

        chars = Char.search([
                ('char', 'not in', [None]),
                ])

        self.assertListEqual(chars, [])

    @with_transaction()
    def test_search_like(self):
        "Test search char like"
        Char = self.Char()
        char, = Char.create([{
                    'char': "Bar",
                    }])

        chars_bar = Char.search([
                ('char', 'like', "Bar"),
                ])
        chars_b = Char.search([
                ('char', 'like', "B%"),
                ])
        chars_foo = Char.search([
                ('char', 'like', "Foo"),
                ])
        chars_f = Char.search([
                ('char', 'like', "F%"),
                ])

        self.assertListEqual(chars_bar, [char])
        self.assertListEqual(chars_b, [char])
        self.assertListEqual(chars_foo, [])
        self.assertListEqual(chars_f, [])

    @with_transaction()
    def test_search_ilike(self):
        "Test search char ilike"
        Char = self.Char()
        char, = Char.create([{
                    'char': "Bar",
                    }])

        chars_bar = Char.search([
                ('char', 'ilike', "bar"),
                ])
        chars_b = Char.search([
                ('char', 'ilike', "b%"),
                ])
        chars_foo = Char.search([
                ('char', 'ilike', "foo"),
                ])
        chars_f = Char.search([
                ('char', 'ilike', "f%"),
                ])

        self.assertListEqual(chars_bar, [char])
        self.assertListEqual(chars_b, [char])
        self.assertListEqual(chars_foo, [])
        self.assertListEqual(chars_f, [])

    @with_transaction()
    def test_search_not_like(self):
        "Test search char not like"
        Char = self.Char()
        char, = Char.create([{
                    'char': "Bar",
                    }])

        chars_bar = Char.search([
                ('char', 'not like', "Bar"),
                ])
        chars_b = Char.search([
                ('char', 'not like', "B%"),
                ])
        chars_foo = Char.search([
                ('char', 'not like', "Foo"),
                ])
        chars_f = Char.search([
                ('char', 'not like', "F%"),
                ])

        self.assertListEqual(chars_bar, [])
        self.assertListEqual(chars_b, [])
        self.assertListEqual(chars_foo, [char])
        self.assertListEqual(chars_f, [char])

    @with_transaction()
    def test_search_not_ilike(self):
        "Test search char not like"
        Char = self.Char()
        char, = Char.create([{
                    'char': "Bar",
                    }])

        chars_bar = Char.search([
                ('char', 'not ilike', "bar"),
                ])
        chars_b = Char.search([
                ('char', 'not ilike', "b%"),
                ])
        chars_foo = Char.search([
                ('char', 'not ilike', "foo"),
                ])
        chars_f = Char.search([
                ('char', 'not ilike', "f%"),
                ])

        self.assertListEqual(chars_bar, [])
        self.assertListEqual(chars_b, [])
        self.assertListEqual(chars_foo, [char])
        self.assertListEqual(chars_f, [char])

    @with_transaction()
    def test_write_unicode(self):
        "Test write char unicode"
        Char = self.Char()
        char, = Char.create([{
                    'char': "Foo",
                    }])

        Char.write([char], {
                'char': "é",
                })

        self.assertEqual(char.char, "é")

    @with_transaction()
    def test_create_strip(self):
        "Test create with stripping"
        Char = self.Char()

        char, = Char.create([{
                    'char': " Foo ",
                    'char_lstripped': " Foo ",
                    'char_rstripped': " Foo ",
                    'char_unstripped': " Foo ",
                    }])

        read_record = Char(char.id)
        self.assertEqual(read_record.char, "Foo")
        self.assertEqual(read_record.char_lstripped, "Foo ")
        self.assertEqual(read_record.char_rstripped, " Foo")
        self.assertEqual(read_record.char_unstripped, " Foo ")

    @with_transaction()
    def test_write_strip(self):
        "Test write with stripping"
        Char = self.Char()

        char, = Char.create([{
                    'char': "Foo",
                    'char_lstripped': "Foo",
                    'char_rstripped': "Foo",
                    'char_unstripped': "Foo",
                    }])

        Char.write([char], {
                'char': " Bar ",
                'char_lstripped': " Bar ",
                'char_rstripped': " Bar ",
                'char_unstripped': " Bar ",
                })
        read_record = Char(char.id)
        self.assertEqual(read_record.char, "Bar")
        self.assertEqual(read_record.char_lstripped, "Bar ")
        self.assertEqual(read_record.char_rstripped, " Bar")
        self.assertEqual(read_record.char_unstripped, " Bar ")

    @with_transaction()
    def test_set_strip(self):
        "Test set with stripping"
        Char = self.Char()

        char = Char()

        char.char = " Foo "
        char.char_lstripped = " Foo "
        char.char_rstripped = " Foo "
        char.char_unstripped = " Foo "
        self.assertEqual(char.char, "Foo")
        self.assertEqual(char.char_lstripped, "Foo ")
        self.assertEqual(char.char_rstripped, " Foo")
        self.assertEqual(char.char_unstripped, " Foo ")


class FieldCharTestCase(TestCase, CommonTestCaseMixin):
    "Test Field Char"

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        activate_module('tests')

    def Char(self):
        return Pool().get('test.char')

    @with_transaction()
    def test_create_with_sql_value(self):
        "Test create with SQL value"
        Char = self.Char()

        char, = Char.create([{'char': Literal('Foo')}])

        self.assertEqual(char.char, "Foo")

    @with_transaction()
    def test_set_sql_value(self):
        "Test cannot set SQL value"
        Char = self.Char()

        char = Char()

        with self.assertRaises(ValueError):
            char.char = Literal('Foo')

    @with_transaction()
    def test_create_without_default(self):
        "Test create char without default"
        Char = Pool().get('test.char')

        char, = Char.create([{}])

        self.assertEqual(char.char, None)

    @with_transaction()
    def test_create_with_default(self):
        "Test create char without default"
        Char = Pool().get('test.char_default')

        char, = Char.create([{}])

        self.assertEqual(char.char, "Test")

    @with_transaction()
    def test_create_required_with_value(self):
        "Test create char required with value"
        Char = Pool().get('test.char_required')

        char, = Char.create([{
                    'char': "Test",
                    }])

        self.assertEqual(char.char, "Test")

    @with_transaction()
    def test_create_required_without_value(self):
        "Test create char required without value"
        Char = Pool().get('test.char_required')

        with self.assertRaises(RequiredValidationError):
            Char.create([{}])

    @with_transaction()
    def test_create_required_with_empty(self):
        "Test create char required with empty"
        Char = Pool().get('test.char_required')

        with self.assertRaises(RequiredValidationError):
            Char.create([{
                        'char': '',
                        }])

    @with_transaction()
    def test_create_size_valid(self):
        "Test create char with size"
        Char = Pool().get('test.char_size')

        char, = Char.create([{
                    'char': "Test",
                    }])

        self.assertEqual(char.char, "Test")

    @with_transaction()
    def test_create_size_invalid(self):
        "Test create char with invalid size"
        Char = Pool().get('test.char_size')

        with self.assertRaises(SizeValidationError):
            Char.create([{
                        'char': "foobar",
                        }])

    @with_transaction()
    def test_create_size_pyson_valid(self):
        "Test create char with PYSON size"
        Char = Pool().get('test.char_size_pyson')

        char, = Char.create([{
                    'char': "Test",
                    'size': 5,
                    }])

        self.assertEqual(char.char, "Test")

    @with_transaction()
    def test_create_size_pyson_invalid(self):
        "Test create char with invalid PYSON size"
        Char = Pool().get('test.char_size_pyson')

        with self.assertRaises(SizeValidationError):
            Char.create([{
                        'char': "foobar",
                        'size': 5,
                        }])

    @with_transaction()
    def test_create_size_pyson_none(self):
        "Test create char with PYSON size as None"
        pool = Pool()
        Char = pool.get('test.char_size_pyson')

        char, = Char.create([{
                    'char': "foo",
                    'size': None,
                    }])

    @with_transaction()
    def test_create_invalid_char(self):
        "Test create char with invalid char"
        Char = Pool().get('test.char')

        with self.assertRaises(ForbiddenCharValidationError):
            Char.create([{
                        'char': "foo\nbar",
                        }])

    @with_transaction()
    def test_write(self):
        "Test write char"
        Char = Pool().get('test.char')
        char, = Char.create([{
                    'char': "Foo",
                    }])

        Char.write([char], {
                'char': "Bar",
                })

        self.assertEqual(char.char, "Bar")

    @with_transaction()
    def test_write_none(self):
        "Test write char None"
        Char = Pool().get('test.char')
        char, = Char.create([{
                    'char': "Foo",
                    }])

        Char.write([char], {
                'char': None,
                })

        self.assertEqual(char.char, None)

    @with_transaction()
    def test_write_size_invalid(self):
        "Test write char with invalid size"
        Char = Pool().get('test.char_size')

        char, = Char.create([{
                    'char': "Test",
                    }])

        with self.assertRaises(SizeValidationError):
            Char.write([char], {
                    'char': 'foobar',
                    })

    @with_transaction()
    def test_create_strip_with_sql_value(self):
        "Test create with stripping with SQL value"
        Char = self.Char()

        char, = Char.create([{
                    'char': Literal(" Foo "),
                    'char_lstripped': Literal(" Foo "),
                    'char_rstripped': Literal(" Foo "),
                    'char_unstripped': Literal(" Foo "),
                    }])

        read_record = Char(char.id)
        self.assertEqual(read_record.char, "Foo")
        self.assertEqual(read_record.char_lstripped, "Foo ")
        self.assertEqual(read_record.char_rstripped, " Foo")
        self.assertEqual(read_record.char_unstripped, " Foo ")

    @with_transaction()
    def test_write_strip_with_sql_value(self):
        "Test write with stripping with SQL value"
        Char = self.Char()

        char, = Char.create([{
                    'char': "Foo",
                    'char_lstripped': "Foo",
                    'char_rstripped': "Foo",
                    'char_unstripped': "Foo",
                    }])

        Char.write([char], {
                'char': Literal(" Bar "),
                'char_lstripped': Literal(" Bar "),
                'char_rstripped': Literal(" Bar "),
                'char_unstripped': Literal(" Bar "),
                })
        read_record = Char(char.id)
        self.assertEqual(read_record.char, "Bar")
        self.assertEqual(read_record.char_lstripped, "Bar ")
        self.assertEqual(read_record.char_rstripped, " Bar")
        self.assertEqual(read_record.char_unstripped, " Bar ")


class FieldCharTranslatedTestCase(TestCase, CommonTestCaseMixin):
    "Test Field Char Translated"

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        activate_module('tests')

    def Char(self):
        return Pool().get('test.char_translate')

    @with_transaction()
    def test_create_with_sql_value(self):
        "Test cannot create with SQL value"
        Char = self.Char()

        with self.assertRaises(ValueError):
            Char.create([{'char': Literal('Foo')}])

    @with_transaction()
    def test_translation_default_language_cache(self):
        """Test set translation for default language does not fill
        transactional cache with former value"""
        pool = Pool()
        Config = pool.get('ir.configuration')
        Char = self.Char()

        with Transaction().set_context(language=Config.get_language()):
            char, = Char.create([{
                        'char': "foo",
                        }])

            char.char = "bar"
            char.save()

            self.assertEqual(char.char, "bar")

    @with_transaction()
    def test_translated(self):
        pool = Pool()
        Language = pool.get('ir.lang')
        Char = self.Char()

        english, = Language.search([('code', '=', 'en')])
        english.translatable = True
        english.save()

        french, = Language.search([('code', '=', 'fr')])
        french.translatable = True
        french.save()

        with Transaction().set_context(language='en'):
            record, = Char.create([{'char': 'foo'}])
        with Transaction().set_context(language='fr'):
            Char.write([record], {'char': 'bar'})

        self.assertEqual(record.char, 'foo')
        self.assertEqual(record.char_translated('en'), 'foo')
        self.assertEqual(record.char_translated('fr'), 'bar')

        with Transaction().set_context(language='fr'):
            record = Char(record.id)

        self.assertEqual(record.char, 'bar')
        self.assertEqual(record.char_translated('en'), 'foo')
        self.assertEqual(record.char_translated('fr'), 'bar')


@unittest.skipUnless(backend.name == 'postgresql',
    "unaccent works only on postgresql")
class FieldCharUnaccentedTestCase(ExtensionTestCase):
    "Test Field Char with unaccented searches"
    extension = 'unaccent'

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        activate_module('tests')

    @with_transaction()
    def test_normal_search(self):
        "Test searches without the unaccented feature"
        Char = Pool().get('test.char_unaccented_off')
        char, = Char.create([{
                    'char': 'Stéphanie',
                    }])

        chars_stephanie = Char.search([
                ('char', 'ilike', 'Stephanie'),
                ])

        self.assertListEqual(chars_stephanie, [])

    @with_transaction()
    def test_accented_search(self):
        "Test searches of accented value"
        Char = Pool().get('test.char_unaccented_on')
        char, = Char.create([{
                    'char': 'Stéphanie',
                    }])

        chars_stephanie = Char.search([
                ('char', 'ilike', 'Stephanie'),
                ])

        self.assertListEqual(chars_stephanie, [char])

    @with_transaction()
    def test_unaccented_search(self):
        "Test searches of unaccented value"
        Char = Pool().get('test.char_unaccented_on')
        char, = Char.create([{
                    'char': 'Stephanie',
                    }])

        chars_stephanie = Char.search([
                ('char', 'ilike', 'Stéphanie'),
                ])
        self.assertListEqual(chars_stephanie, [char])

    @with_transaction()
    def test_unaccented_translated_search(self):
        "Test unaccented translated search"
        pool = Pool()
        Char = pool.get('test.char_unaccented_translate')
        Lang = pool.get('ir.lang')

        lang, = Lang.search([
                ('translatable', '=', False),
                ('code', '!=', 'en'),
                ], limit=1)
        lang.translatable = True
        lang.save()
        char, = Char.create([{
                    'char': 'School',
                    }])

        with Transaction().set_context(lang=lang.code):
            trans_char = Char(char.id)
            trans_char.char = 'École'
            trans_char.save()

            chars_ecole = Char.search([
                    ('char', 'ilike', 'Ecole'),
                    ])
            self.assertListEqual(chars_ecole, [char])


@unittest.skipUnless(backend.name == 'postgresql',
    "similarity works only on postgresql")
class FieldCharSimilarityTestCase(ExtensionTestCase):
    "Test Field Char with similarity searches"
    extension = 'pg_trgm'

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        activate_module('tests')

    def _test_search(self, Model):
        record1, record2 = Model.create([{
                    'char': "word",
                    }, {
                    'char': "Foo",
                    }])

        with Transaction().set_context(search_similarity=0.3):
            self.assertListEqual(Model.search([
                        ('char', 'ilike', 'two words'),
                        ]), [record1])

    @with_transaction()
    def test_search(self):
        "Test search"
        pool = Pool()
        self._test_search(pool.get('test.char'))

    @with_transaction()
    def test_search_translated(self):
        "Test search translated"
        pool = Pool()
        self._test_search(pool.get('test.char_translate'))

    @with_transaction()
    def test_search_empty(self):
        "Test a full text search with an empty string"
        pool = Pool()
        Char = pool.get('test.char')

        record1, record2 = Char.create([{
                    'char': "word",
                    }, {
                    'char': "",
                    }])

        with Transaction().set_context(search_similarity=0.3):
            self.assertEqual(
                Char.search([('char', 'ilike', '')]),
                [record2])

    @with_transaction()
    def test_search_null(self):
        "Test a full text search with NULL"
        pool = Pool()
        Char = pool.get('test.char')

        record1, record2 = Char.create([{
                    'char': "word",
                    }, {
                    'char': None,
                    }])

        with Transaction().set_context(search_similarity=0.3):
            self.assertEqual(
                Char.search([('char', 'ilike', None)]),
                [record2])

    def _test_order(self, Model):
        record1, record2 = Model.create([{
                    'char': "word",
                    }, {
                    'char': "Foo",
                    }])
        with Transaction().set_context({
                    '%s.char.order' % Model.__name__: 'foo bar',
                    }):
            self.assertListEqual(Model.search([
                        ], order=[('char', 'DESC')]),
                [record2, record1])

    @with_transaction()
    def test_order(self):
        "Test order"
        pool = Pool()
        self._test_order(pool.get('test.char'))

    @with_transaction()
    def test_order_translated(self):
        "Test order translated"
        pool = Pool()
        self._test_order(pool.get('test.char_translate'))
