# 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 trytond import backend
from trytond.model.exceptions import (
    RequiredValidationError, SelectionValidationError)
from trytond.pool import Pool
from trytond.tests.test_tryton import activate_module, with_transaction
from trytond.transaction import Transaction


class FieldMultiSelectionTestCase(unittest.TestCase):
    "Test Field MultiSelection"

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

    @with_transaction()
    def test_create(self):
        "Test create multi-selection"
        Selection = Pool().get('test.multi_selection')

        selection, selection_none = Selection.create([{
                    'selects': ['foo', 'bar'],
                    }, {
                    'selects': None,
                    }])

        self.assertEqual(selection.selects, ('bar', 'foo'))
        self.assertEqual(selection_none.selects, ())

    @with_transaction()
    def test_create_not_in(self):
        "Test create multi-selection not in selection"
        Selection = Pool().get('test.multi_selection')

        with self.assertRaises(SelectionValidationError):
            Selection.create([{
                        'selects': ('invalid'),
                        }])

    @with_transaction()
    def test_create_dynamic(self):
        "Test create dynamic selection"
        Selection = Pool().get('test.multi_selection')

        selection_foo, selection_bar = Selection.create([{
                    'selects': ['foo'],
                    'dyn_selects': ['foo'],
                    }, {
                    'selects': ['bar'],
                    'dyn_selects': ['baz'],
                    }])

        self.assertEqual(selection_foo.dyn_selects, ('foo',))
        self.assertEqual(selection_bar.dyn_selects, ('baz',))

    @with_transaction()
    def test_create_dynamic_none(self):
        "Test create dynamic selection None"
        Selection = Pool().get('test.multi_selection')

        selection, = Selection.create([{
                    'selects': ['foo'],
                    'dyn_selects': None,
                    }])

        self.assertEqual(selection.dyn_selects, ())

    @with_transaction()
    def test_create_dynamic_not_in(self):
        "Test create dynamic selection not in"
        Selection = Pool().get('test.multi_selection')

        with self.assertRaises(SelectionValidationError):
            Selection.create([{
                    'selects': ['foo'],
                    'dyn_selects': ['foo', 'bar'],
                    }])

    @with_transaction()
    def test_create_static(self):
        "Test create static selection"
        Selection = Pool().get('test.multi_selection')

        selection, = Selection.create([{
                    'static_selects': ['foo', 'bar'],
                    }])

        self.assertEqual(selection.static_selects, ('bar', 'foo'))

    @with_transaction()
    def test_create_static_none(self):
        "Test create static selection None"
        Selection = Pool().get('test.multi_selection')

        selection, = Selection.create([{
                    'static_selects': None,
                    }])

        self.assertEqual(selection.static_selects, ())

    @with_transaction()
    def test_create_static_not_in(self):
        "Test create static selection not in"
        Selection = Pool().get('test.multi_selection')

        with self.assertRaises(SelectionValidationError):
            Selection.create([{
                    'static_selects': ['foo', 'bar', 'invalid'],
                    }])

    @with_transaction()
    def test_create_required_with_value(self):
        "Test create selection required with value"
        Selection = Pool().get('test.multi_selection_required')

        selection, = Selection.create([{
                    'selects': ['foo', 'bar'],
                    }])

        self.assertEqual(selection.selects, ('bar', 'foo'))

    @with_transaction()
    def test_create_required_without_value(self):
        "Test create selection required without value"
        Selection = Pool().get('test.multi_selection_required')

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

    @with_transaction()
    def test_create_required_none(self):
        "Test create selection required without value"
        Selection = Pool().get('test.multi_selection_required')

        with self.assertRaises(RequiredValidationError):
            Selection.create([{
                        'selects': None,
                        }])

    @with_transaction()
    def test_create_required_empty(self):
        "Test create selection required with empty value"
        Selection = Pool().get('test.multi_selection_required')

        with self.assertRaises(RequiredValidationError):
            Selection.create([{
                        'selects': [],
                        }])

    @with_transaction()
    def test_write(self):
        "Test write selection"
        Selection = Pool().get('test.multi_selection')
        selection, = Selection.create([{
                    'selects': ['foo'],
                    }])

        Selection.write([selection], {
                'selects': ['foo', 'bar'],
                })

        self.assertEqual(selection.selects, ('bar', 'foo'))

    @with_transaction()
    def test_string(self):
        "Test string selection"
        Selection = Pool().get('test.multi_selection')
        selection, = Selection.create([{
                    'selects': ['foo', 'bar'],
                    }])

        self.assertEqual(selection.selects_string, ["Bar", "Foo"])

    @with_transaction()
    def test_string_none(self):
        "Test string selection none"
        Selection = Pool().get('test.multi_selection')
        selection, = Selection.create([{
                    'selects': None,
                    }])

        self.assertEqual(selection.selects_string, [])

    @with_transaction()
    def test_search_equals(self):
        "Test search selection equals"
        Selection = Pool().get('test.multi_selection')
        selection, = Selection.create([{
                    'selects': ['bar', 'foo'],
                    }])

        foo_bar = Selection.search([
                ('selects', '=', ['foo', 'bar']),
                ])
        foo = Selection.search([
                ('selects', '=', ['foo']),
                ])

        self.assertEqual(foo_bar, [selection])
        self.assertEqual(foo, [])

    @with_transaction()
    def test_search_equals_string(self):
        "Test search selection equals string"
        Selection = Pool().get('test.multi_selection')
        selection, = Selection.create([{
                    'selects': ['foo'],
                    }])

        foo = Selection.search([
                ('selects', '=', 'foo'),
                ])

        self.assertEqual(foo, [])

    @with_transaction()
    def test_search_equals_none(self):
        "Test search selection equals"
        Selection = Pool().get('test.multi_selection')
        selection, = Selection.create([{
                    'selects': None,
                    }])

        selections = Selection.search([
                ('selects', '=', None),
                ])

        self.assertEqual(selections, [selection])

    @with_transaction()
    def test_search_in_string(self):
        "Test search selection in string"
        Selection = Pool().get('test.multi_selection')
        selection, = Selection.create([{
                    'selects': ['foo', 'bar'],
                    }])

        foo = Selection.search([
                ('selects', 'in', 'foo'),
                ])
        baz = Selection.search([
                ('selects', 'in', 'baz'),
                ])

        self.assertEqual(foo, [selection])
        self.assertEqual(baz, [])

    @with_transaction()
    def test_search_not_in_string(self):
        "Test search selection not in string"
        Selection = Pool().get('test.multi_selection')
        selection, = Selection.create([{
                    'selects': ['foo', 'bar'],
                    }])

        foo = Selection.search([
                ('selects', 'not in', 'foo'),
                ])
        baz = Selection.search([
                ('selects', 'not in', 'baz'),
                ])

        self.assertEqual(foo, [])
        self.assertEqual(baz, [selection])

    @with_transaction()
    def test_search_in_list(self):
        "Test search selection in list"
        Selection = Pool().get('test.multi_selection')
        selection, = Selection.create([{
                    'selects': ['foo', 'bar'],
                    }])

        foo = Selection.search([
                ('selects', 'in', ['foo']),
                ])
        baz = Selection.search([
                ('selects', 'in', ['baz']),
                ])
        foo_baz = Selection.search([
                ('selects', 'in', ['foo', 'baz']),
                ])
        empty = Selection.search([
                ('selects', 'in', []),
                ])

        self.assertEqual(foo, [selection])
        self.assertEqual(baz, [])
        self.assertEqual(foo_baz, [selection])
        self.assertEqual(empty, [])

    @with_transaction()
    def test_search_not_in_list(self):
        "Test search selection not in list"
        Selection = Pool().get('test.multi_selection')
        selection, = Selection.create([{
                    'selects': ['foo', 'bar'],
                    }])

        foo = Selection.search([
                ('selects', 'not in', ['foo']),
                ])
        baz = Selection.search([
                ('selects', 'not in', ['baz']),
                ])
        foo_baz = Selection.search([
                ('selects', 'not in', ['foo', 'baz']),
                ])
        empty = Selection.search([
                ('selects', 'not in', []),
                ])

        self.assertEqual(foo, [])
        self.assertEqual(baz, [selection])
        self.assertEqual(foo_baz, [])
        self.assertEqual(empty, [selection])

    @with_transaction()
    def test_read_string(self):
        "Test reading value and string"
        Selection = Pool().get('test.multi_selection')
        foo, foo_bar, null = Selection.create([{
                    'selects': ['foo'],
                    }, {
                    'selects': ['foo', 'bar'],
                    }, {
                    'selects': [],
                    }])

        foo_read, foo_bar_read, null_read = Selection.read(
            [foo.id, foo_bar.id, null.id], ['selects:string'])

        self.assertEqual(foo_read['selects:string'], ["Foo"])
        self.assertEqual(foo_bar_read['selects:string'], ["Bar", "Foo"])
        self.assertEqual(null_read['selects:string'], [])

    @with_transaction()
    def test_read_string_dynamic_selection(self):
        "Test reading value and string of a dynamic selection"
        Selection = Pool().get('test.multi_selection')
        foo, bar, null = Selection.create([{
                    'selects': ['foo'],
                    'dyn_selects': ['foo', 'foobar'],
                    }, {
                    'dyn_selects': ['bar', 'baz'],
                    }, {
                    'dyn_selects': [],
                    }])

        foo_read, bar_read, null_read = Selection.read(
            [foo.id, bar.id, null.id], ['dyn_selects:string'])

        self.assertEqual(foo_read['dyn_selects:string'], ["Foo", "FooBar"])
        self.assertEqual(bar_read['dyn_selects:string'], ["Bar", "Baz"])
        self.assertEqual(null_read['dyn_selects:string'], [])


@unittest.skipIf(
    backend.name != 'postgresql', 'jsonb only supported by postgresql')
class FieldMultiSelectionJSONBTestCase(FieldMultiSelectionTestCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.setup_model()

    @classmethod
    @with_transaction()
    def setup_model(cls):
        connection = Transaction().connection
        if backend.Database().get_version(connection) < (9, 2):
            return
        pool = Pool()
        for model in ['test.multi_selection', 'test.multi_selection_required']:
            Model = pool.get(model)
            cursor = connection.cursor()
            for name, field in Model._fields.items():
                if field._type == 'multiselection':
                    cursor.execute('ALTER TABLE "%s" '
                        'ALTER COLUMN %s TYPE json USING %s::json' % (
                            Model._table, name, name))
        Transaction().commit()
