"""
This module tests work with IPROTO pushed messages.
"""
# pylint: disable=missing-class-docstring,missing-function-docstring,duplicate-code

import sys
import unittest
import tarantool
from .lib.tarantool_server import TarantoolServer
from .utils import assert_admin_success


def create_server():
    srv = TarantoolServer()
    srv.script = 'test/suites/box.lua'
    srv.start()
    resp = srv.admin("""
        box.schema.user.create('test', {password = 'test', if_not_exists = true})
        box.schema.user.grant('test', 'read,write,execute', 'universe',
                              nil, {if_not_exists = true})

        function server_function()
            x = {0,0}
            while x[1] < 3 do
                x[1] = x[1] + 1
                box.session.push(x)
            end
            return x
        end

        box.schema.create_space(
            'tester', {
            format = {
                {name = 'id', type = 'unsigned'},
                {name = 'name', type = 'string'},
            }
        })

        box.space.tester:create_index(
            'primary_index', {
            parts = {
                {field = 1, type = 'unsigned'},
            },
            if_not_exists = true,
        })

        function on_replace_callback()
            x = {0,0}
            while x[1] < 300 do
                x[1] = x[1] + 100
                box.session.push(x)
            end
        end

        box.space.tester:on_replace(
            on_replace_callback
        )

        return true
    """)
    assert_admin_success(resp)

    return srv


# Callback for on_push arg (for testing purposes).
def push_callback(data, on_push_ctx):
    data[0][1] = data[0][1] + 1
    on_push_ctx.append(data)


class TestSuitePush(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print(' PUSH '.center(70, '='), file=sys.stderr)
        print('-' * 70, file=sys.stderr)
        # Create server and extract helpful fields for tests.
        cls.srv = create_server()
        cls.host = cls.srv.host
        cls.port = cls.srv.args['primary']

    def setUp(self):
        # Open connection, connection pool and mesh connection to instance.
        self.conn = tarantool.Connection(host=self.host, port=self.port,
                                         user='test', password='test')
        self.conn_pool = tarantool.ConnectionPool([{'host': self.host, 'port': self.port}],
                                                  user='test', password='test')
        self.mesh_conn = tarantool.MeshConnection(host=self.host, port=self.port,
                                                  user='test', password='test')

    push_test_cases = {
        'call': {
            'input': {
                'args': ['server_function'],
                'kwargs': {
                    'on_push': push_callback,
                    # on_push_ctx must be set manually when running the test.
                    'on_push_ctx': None,
                }
            },
            'output': {
                'callback_res': [[[1, 1]], [[2, 1]], [[3, 1]]],
                'resp': [3, 0],
            },
        },
        'eval': {
            'input': {
                'args': ['return server_function()'],
                'kwargs': {
                    'on_push': push_callback,
                    # on_push_ctx must be set manually when running the test.
                    'on_push_ctx': None,
                }
            },
            'output': {
                'callback_res': [[[1, 1]], [[2, 1]], [[3, 1]]],
                'resp': [3, 0],
            },
        },
        'insert': {
            'input': {
                'args': ['tester', (1, 'Mike')],
                'kwargs': {
                    'on_push': push_callback,
                    # on_push_ctx must be set manually when running the test.
                    'on_push_ctx': None,
                }
            },
            'output': {
                'callback_res': [[[100, 1]], [[200, 1]], [[300, 1]]],
                'resp': [1, 'Mike'],
            },
        },
        'replace': {
            'input': {
                'args': ['tester', (1, 'Bill')],
                'kwargs': {
                    'on_push': push_callback,
                    # on_push_ctx must be set manually when running the test.
                    'on_push_ctx': None,
                }
            },
            'output': {
                'callback_res': [[[100, 1]], [[200, 1]], [[300, 1]]],
                'resp': [1, 'Bill'],
            },
        },
        'update': {
            'input': {
                'args': ['tester', 1],
                'kwargs': {
                    'op_list': [],
                    'on_push': push_callback,
                    # on_push_ctx must be set manually when running the test.
                    'on_push_ctx': None,
                }
            },
            'output': {
                'callback_res': [[[100, 1]], [[200, 1]], [[300, 1]]],
                'resp': [1, 'Bill'],
            },
        },
        'upsert': {
            'input': {
                'args': ['tester', (1, 'Bill')],
                'kwargs': {
                    'op_list': [],
                    'on_push': push_callback,
                    # on_push_ctx must be set manually when running the test.
                    'on_push_ctx': None,
                }
            },
            'output': {
                'callback_res': [[[100, 1]], [[200, 1]], [[300, 1]]],
                # resp not used in the test output.
                'resp': None,
            },
        },
        'delete': {
            'input': {
                'args': ['tester', 1],
                'kwargs': {
                    'on_push': push_callback,
                    # on_push_ctx must be set manually when running the test.
                    'on_push_ctx': None,
                }
            },
            'output': {
                'callback_res': [[[100, 1]], [[200, 1]], [[300, 1]]],
                'resp': [1, 'Bill'],
            },
        },
    }

    def test_00_00_push_via_connection(self):
        for name, case in self.push_test_cases.items():
            with self.subTest(name=name):
                callback_res = []
                testing_function = getattr(self.conn, name)
                case['input']['kwargs']['on_push_ctx'] = callback_res
                resp = testing_function(
                    *case['input']['args'],
                    **case['input']['kwargs']
                )
                self.assertEqual(callback_res, case['output']['callback_res'])
                if case['output']['resp'] is not None:
                    self.assertEqual(resp.data[0], case['output']['resp'])

    def test_00_01_push_via_mesh_connection(self):
        for name, case in self.push_test_cases.items():
            with self.subTest(name=name):
                callback_res = []
                testing_function = getattr(self.mesh_conn, name)
                case['input']['kwargs']['on_push_ctx'] = callback_res
                resp = testing_function(
                    *case['input']['args'],
                    **case['input']['kwargs']
                )
                self.assertEqual(callback_res, case['output']['callback_res'])
                if case['output']['resp'] is not None:
                    self.assertEqual(resp.data[0], case['output']['resp'])

    def test_00_02_push_via_connection_pool(self):
        for name, case in self.push_test_cases.items():
            with self.subTest(name=name):
                callback_res = []
                testing_function = getattr(self.conn_pool, name)
                case['input']['kwargs']['on_push_ctx'] = callback_res
                resp = testing_function(
                    *case['input']['args'],
                    **case['input']['kwargs'],
                    mode=tarantool.Mode.RW
                )
                self.assertEqual(callback_res, case['output']['callback_res'])
                if case['output']['resp'] is not None:
                    self.assertEqual(resp.data[0], case['output']['resp'])

    def tearDown(self):
        # Close connection, connection pool and mesh connection to instance.
        self.conn.close()
        self.conn_pool.close()
        self.mesh_conn.close()

    @classmethod
    def tearDownClass(cls):
        # Stop instance.
        cls.srv.stop()
        cls.srv.clean()
