import asyncio
from tests import base
from tests._testutils import run_until_complete

from aiomysql import ProgrammingError, Cursor


class TestCursor(base.AIOPyMySQLTestCase):

    @asyncio.coroutine
    def _prepare(self, conn):
        cur = yield from conn.cursor()
        yield from cur.execute("DROP TABLE IF EXISTS tbl;")

        yield from cur.execute("""CREATE TABLE tbl (
                 id MEDIUMINT NOT NULL AUTO_INCREMENT,
                 name VARCHAR(255) NOT NULL,
                 PRIMARY KEY (id));""")

        for i in [(1, 'a'), (2, 'b'), (3, 'c')]:
            yield from cur.execute("INSERT INTO tbl VALUES(%s, %s)", i)
        yield from cur.execute("DROP TABLE IF EXISTS tbl2")
        yield from cur.execute("""CREATE TABLE tbl2
                                  (id int, name varchar(255))""")
        yield from conn.commit()

    @asyncio.coroutine
    def _prepare_procedure(self, conn):
        cur = yield from conn.cursor()
        yield from cur.execute("DROP PROCEDURE IF EXISTS myinc;")
        yield from cur.execute("""CREATE PROCEDURE myinc(p1 INT)
                               BEGIN
                                   SELECT p1 + 1;
                               END
                               """)
        yield from conn.commit()

    @run_until_complete
    def test_description(self):
        conn = self.connections[0]
        yield from self._prepare(conn)
        cur = yield from conn.cursor()
        self.assertEqual(None, cur.description)
        yield from cur.execute('SELECT * from tbl;')

        self.assertEqual(len(cur.description), 2,
                         'cursor.description describes too many columns')

        self.assertEqual(len(cur.description[0]), 7,
                         'cursor.description[x] tuples must have '
                         '7 elements')

        self.assertEqual(cur.description[0][0].lower(), 'id',
                         'cursor.description[x][0] must return column '
                         'name')

        self.assertEqual(cur.description[1][0].lower(), 'name',
                         'cursor.description[x][0] must return column '
                         'name')

        # Make sure self.description gets reset, cursor should be
        # set to None in case of none resulting queries like DDL
        yield from cur.execute('DROP TABLE IF EXISTS foobar;')
        self.assertEqual(None, cur.description)

    @run_until_complete
    def test_cursor_properties(self):
        conn = self.connections[0]
        cur = yield from conn.cursor()
        self.assertIs(cur.connection, conn)
        cur.setinputsizes()
        cur.setoutputsizes()
        self.assertEqual(cur.echo, False)

    @run_until_complete
    def test_scroll_relative(self):
        conn = self.connections[0]
        yield from self._prepare(conn)
        cur = yield from conn.cursor()
        yield from cur.execute('SELECT * FROM tbl;')
        yield from cur.scroll(1)
        ret = yield from cur.fetchone()
        self.assertEqual((2, 'b'), ret)

    @run_until_complete
    def test_scroll_absolute(self):
        conn = self.connections[0]
        yield from self._prepare(conn)
        cur = yield from conn.cursor()
        yield from cur.execute('SELECT * FROM tbl;')
        yield from cur.scroll(2, mode='absolute')
        ret = yield from cur.fetchone()
        self.assertEqual((3, 'c'), ret)

    @run_until_complete
    def test_scroll_errors(self):
        conn = self.connections[0]
        cur = yield from conn.cursor()

        with self.assertRaises(ProgrammingError):
            yield from cur.scroll(2, mode='absolute')

        cur = yield from conn.cursor()
        yield from cur.execute('SELECT * FROM tbl;')

        with self.assertRaises(ProgrammingError):
            yield from cur.scroll(2, mode='not_valid_mode')

    @run_until_complete
    def test_scroll_index_error(self):
        conn = self.connections[0]
        yield from self._prepare(conn)
        cur = yield from conn.cursor()
        yield from cur.execute('SELECT * FROM tbl;')
        with self.assertRaises(IndexError):
            yield from cur.scroll(1000)

    @run_until_complete
    def test_close(self):
        conn = self.connections[0]
        cur = yield from conn.cursor()
        yield from cur.close()
        self.assertTrue(cur.closed)
        with self.assertRaises(ProgrammingError):
            yield from cur.execute('SELECT 1')
        # try to close for second time
        yield from cur.close()

    @run_until_complete
    def test_arraysize(self):
        conn = self.connections[0]
        cur = yield from conn.cursor()
        self.assertEqual(1, cur.arraysize)
        cur.arraysize = 10
        self.assertEqual(10, cur.arraysize)

    @run_until_complete
    def test_rows(self):
        conn = self.connections[0]
        yield from self._prepare(conn)

        cur = yield from conn.cursor()
        yield from cur.execute('SELECT * from tbl')
        self.assertEqual(3, cur.rowcount)
        self.assertEqual(0, cur.rownumber)
        yield from cur.fetchone()
        self.assertEqual(1, cur.rownumber)
        self.assertEqual(None, cur.lastrowid)
        yield from cur.execute('INSERT INTO tbl VALUES (%s, %s)', (4, 'd'))
        self.assertNotEqual(0, cur.lastrowid)
        yield from conn.commit()

    @run_until_complete
    def test_callproc(self):
        conn = yield from self.connect()
        yield from self._prepare_procedure(conn)
        cur = yield from conn.cursor()
        yield from cur.callproc('myinc', [1])
        ret = yield from cur.fetchone()
        self.assertEqual((2,), ret)
        yield from cur.close()
        with self.assertRaises(ProgrammingError):
            yield from cur.callproc('myinc', [1])
        conn.close()

    @run_until_complete
    def test_fetchone_no_result(self):
        # test a fetchone() with no rows
        conn = self.connections[0]
        c = yield from conn.cursor()
        yield from c.execute("create table test_nr (b varchar(32))")
        try:
            data = "pymysql"
            yield from c.execute("insert into test_nr (b) values (%s)",
                                 (data,))
            r = yield from c.fetchone()
            self.assertEqual(None, r)
        finally:
            yield from c.execute("drop table test_nr")

    @run_until_complete
    def test_fetchmany_no_result(self):
        conn = self.connections[0]
        cur = yield from conn.cursor()
        yield from cur.execute('DROP TABLE IF EXISTS foobar;')
        r = yield from cur.fetchmany()
        self.assertEqual([], r)

    @run_until_complete
    def test_fetchall_no_result(self):
        # test a fetchone() with no rows
        conn = self.connections[0]
        cur = yield from conn.cursor()
        yield from cur.execute('DROP TABLE IF EXISTS foobar;')
        r = yield from cur.fetchall()
        self.assertEqual([], r)

    @run_until_complete
    def test_fetchall_with_scroll(self):
        conn = self.connections[0]
        yield from self._prepare(conn)
        cur = yield from conn.cursor()
        yield from cur.execute('SELECT * FROM tbl;')
        yield from cur.scroll(1)
        ret = yield from cur.fetchall()
        self.assertEquals(((2, 'b'), (3, 'c')), ret)

    @run_until_complete
    def test_aggregates(self):
        """ test aggregate functions """
        conn = self.connections[0]
        c = yield from conn.cursor()
        try:
            yield from c.execute('create table test_aggregates (i integer)')
            for i in range(0, 10):
                yield from c.execute(
                    'insert into test_aggregates (i) values (%s)', (i,))
            yield from c.execute('select sum(i) from test_aggregates')
            r, = yield from c.fetchone()
            self.assertEqual(sum(range(0, 10)), r)
        finally:
            yield from c.execute('drop table test_aggregates')

    @run_until_complete
    def test_single_tuple(self):
        """ test a single tuple """
        conn = self.connections[0]
        c = yield from conn.cursor()
        try:
            yield from c.execute(
                "create table mystuff (id integer primary key)")
            yield from c.execute("insert into mystuff (id) values (1)")
            yield from c.execute("insert into mystuff (id) values (2)")
            yield from c.execute("select id from mystuff where id in %s",
                                 ((1,),))
            r = yield from c.fetchall()
            self.assertEqual([(1,)], list(r))
        finally:
            yield from c.execute("drop table mystuff")

    @run_until_complete
    def test_executemany(self):
        conn = self.connections[0]
        yield from self._prepare(conn)
        cur = yield from conn.cursor()
        self.assertEqual(None, cur.description)
        args = [1, 2, 3]
        row_count = yield from cur.executemany(
            'SELECT * FROM tbl WHERE id  = %s;', args)
        self.assertEqual(row_count, 3)
        r = yield from cur.fetchall()
        # TODO: if this right behaviour
        self.assertEquals(((3, 'c'),),  r)

        # calling execute many without args
        row_count = yield from cur.executemany('SELECT 1;', ())
        self.assertIsNone(row_count)

    @run_until_complete
    def test_custom_cursor(self):
        class MyCursor(Cursor):
            pass
        conn = self.connections[0]
        cur = yield from conn.cursor(MyCursor)
        self.assertIsInstance(cur, MyCursor)
        yield from cur.execute("SELECT 42;")
        (r, ) = yield from cur.fetchone()
        self.assertEqual(r, 42)

    @run_until_complete
    def test_custom_cursor_not_cursor_subclass(self):
        class MyCursor2:
            pass
        conn = self.connections[0]
        with self.assertRaises(TypeError):
            yield from conn.cursor(MyCursor2)
