import asyncio
import aiomysql
from aiomysql import connect, sa, Cursor

import os
import unittest
from unittest import mock

from sqlalchemy import MetaData, Table, Column, Integer, String
from sqlalchemy.schema import DropTable, CreateTable


meta = MetaData()
tbl = Table('sa_tbl', meta,
            Column('id', Integer, nullable=False,
                   primary_key=True),
            Column('name', String(255)))


class TestSAConnection(unittest.TestCase):
    def setUp(self):
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(None)
        self.host = os.environ.get('MYSQL_HOST', 'localhost')
        self.port = os.environ.get('MYSQL_PORT', 3306)
        self.user = os.environ.get('MYSQL_USER', 'root')
        self.db = os.environ.get('MYSQL_DB', 'test_pymysql')
        self.password = os.environ.get('MYSQL_PASSWORD', '')

    def tearDown(self):
        self.loop.close()

    @asyncio.coroutine
    def connect(self, **kwargs):
        conn = yield from connect(db=self.db,
                                  user=self.user,
                                  password=self.password,
                                  host=self.host,
                                  loop=self.loop,
                                  **kwargs)
        yield from conn.autocommit(True)
        cur = yield from conn.cursor()
        yield from cur.execute("DROP TABLE IF EXISTS sa_tbl")
        yield from cur.execute("CREATE TABLE sa_tbl "
                               "(id serial, name varchar(255))")
        yield from cur.execute("INSERT INTO sa_tbl (name)"
                               "VALUES ('first')")

        yield from cur._connection.commit()
        # yield from cur.close()
        engine = mock.Mock()
        engine.dialect = sa.engine._dialect
        return sa.SAConnection(conn, engine)

    def test_execute_text_select(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute("SELECT * FROM sa_tbl;")
            self.assertIsInstance(res.cursor, Cursor)
            self.assertEqual(('id', 'name'), res.keys())
            rows = [r for r in res]
            self.assertTrue(res.closed)
            self.assertIsNone(res.cursor)
            self.assertEqual(1, len(rows))
            row = rows[0]
            self.assertEqual(1, row[0])
            self.assertEqual(1, row['id'])
            self.assertEqual(1, row.id)
            self.assertEqual('first', row[1])
            self.assertEqual('first', row['name'])
            self.assertEqual('first', row.name)
            # TODO: fix this
            yield from conn._connection.commit()
        self.loop.run_until_complete(go())

    def test_execute_sa_select(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute(tbl.select())
            self.assertIsInstance(res.cursor, Cursor)
            self.assertEqual(('id', 'name'), res.keys())
            rows = [r for r in res]
            self.assertTrue(res.closed)
            self.assertIsNone(res.cursor)
            self.assertTrue(res.returns_rows)

            self.assertEqual(1, len(rows))
            row = rows[0]
            self.assertEqual(1, row[0])
            self.assertEqual(1, row['id'])
            self.assertEqual(1, row.id)
            self.assertEqual('first', row[1])
            self.assertEqual('first', row['name'])
            self.assertEqual('first', row.name)
            # TODO: fix this
            yield from conn._connection.commit()

        self.loop.run_until_complete(go())

    def test_execute_sa_insert_with_dict(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert(), {"id": 2, "name": "second"})

            res = yield from conn.execute(tbl.select())
            rows = list(res)
            self.assertEqual(2, len(rows))
            self.assertEqual((1, 'first'), rows[0])
            self.assertEqual((2, 'second'), rows[1])

        self.loop.run_until_complete(go())

    def test_execute_sa_insert_with_tuple(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert(), (2, "second"))

            res = yield from conn.execute(tbl.select())
            rows = list(res)
            self.assertEqual(2, len(rows))
            self.assertEqual((1, 'first'), rows[0])
            self.assertEqual((2, 'second'), rows[1])

        self.loop.run_until_complete(go())

    def test_execute_sa_insert_named_params(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert(), id=2, name="second")

            res = yield from conn.execute(tbl.select())
            rows = list(res)
            self.assertEqual(2, len(rows))
            self.assertEqual((1, 'first'), rows[0])
            self.assertEqual((2, 'second'), rows[1])

        self.loop.run_until_complete(go())

    def test_execute_sa_insert_positional_params(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert(), 2, "second")

            res = yield from conn.execute(tbl.select())
            rows = list(res)
            self.assertEqual(2, len(rows))
            self.assertEqual((1, 'first'), rows[0])
            self.assertEqual((2, 'second'), rows[1])

        self.loop.run_until_complete(go())

    def test_scalar(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.scalar(tbl.count())
            self.assertEqual(1, res)

        self.loop.run_until_complete(go())

    def test_scalar_None(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.delete())
            res = yield from conn.scalar(tbl.select())
            self.assertIsNone(res)
            # TODO: fix this
            yield from conn._connection.commit()

        self.loop.run_until_complete(go())

    def test_row_proxy(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute(tbl.select())
            rows = [r for r in res]
            row = rows[0]
            row2 = yield from (yield from conn.execute(tbl.select())).first()
            self.assertEqual(2, len(row))
            self.assertEqual(['id', 'name'], list(row))
            self.assertIn('id', row)
            self.assertNotIn('unknown', row)
            self.assertEqual('first', row.name)
            self.assertEqual('first', row[tbl.c.name])
            with self.assertRaises(AttributeError):
                row.unknown
            self.assertEqual("(1, 'first')", repr(row))
            self.assertEqual((1, 'first'), row.as_tuple())
            self.assertNotEqual((555, 'other'), row.as_tuple())
            self.assertEqual(row2, row)
            self.assertFalse(row2 != row)
            self.assertNotEqual(5, row)
            # TODO: fix this
            yield from conn._connection.commit()

        self.loop.run_until_complete(go())

    def test_insert(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute(tbl.insert().values(name='second'))
            self.assertEqual(1, res.rowcount)
            self.assertEqual(2, res.lastrowid)

        self.loop.run_until_complete(go())

    def test_raw_insert(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(
                "INSERT INTO sa_tbl (name) VALUES ('third')")
            res = yield from conn.execute(tbl.select())
            self.assertEqual(2, res.rowcount)
            self.assertEqual(('id', 'name'), res.keys())
            self.assertTrue(res.returns_rows)

            rows = [r for r in res]
            self.assertEqual(2, len(rows))
            self.assertEqual(2, rows[1].id)
        self.loop.run_until_complete(go())

    def test_raw_insert_with_params(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute(
                "INSERT INTO sa_tbl (id, name) VALUES (%s, %s)",
                2, 'third')
            res = yield from conn.execute(tbl.select())
            self.assertEqual(2, res.rowcount)
            self.assertEqual(('id', 'name'), res.keys())
            self.assertTrue(res.returns_rows)

            rows = [r for r in res]
            self.assertEqual(2, len(rows))
            self.assertEqual(2, rows[1].id)
        self.loop.run_until_complete(go())

    def test_raw_insert_with_params_dict(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute(
                "INSERT INTO sa_tbl (id, name) VALUES (%(id)s, %(name)s)",
                {'id': 2, 'name': 'third'})
            res = yield from conn.execute(tbl.select())
            self.assertEqual(2, res.rowcount)
            self.assertEqual(('id', 'name'), res.keys())
            self.assertTrue(res.returns_rows)

            rows = [r for r in res]
            self.assertEqual(2, len(rows))
            self.assertEqual(2, rows[1].id)
        self.loop.run_until_complete(go())

    def test_raw_insert_with_named_params(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute(
                "INSERT INTO sa_tbl (id, name) VALUES (%(id)s, %(name)s)",
                id=2, name='third')
            res = yield from conn.execute(tbl.select())
            self.assertEqual(2, res.rowcount)
            self.assertEqual(('id', 'name'), res.keys())
            self.assertTrue(res.returns_rows)

            rows = [r for r in res]
            self.assertEqual(2, len(rows))
            self.assertEqual(2, rows[1].id)
        self.loop.run_until_complete(go())

    def test_raw_insert_with_executemany(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            with self.assertRaises(sa.ArgumentError):
                yield from conn.execute(
                    "INSERT INTO sa_tbl (id, name) VALUES (%(id)s, %(name)s)",
                    [(2, 'third'), (3, 'forth')])
        self.loop.run_until_complete(go())

    def test_delete(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()

            res = yield from conn.execute(tbl.delete().where(tbl.c.id == 1))

            self.assertEqual((), res.keys())
            self.assertEqual(1, res.rowcount)
            self.assertFalse(res.returns_rows)
            self.assertTrue(res.closed)
            self.assertIsNone(res.cursor)

        self.loop.run_until_complete(go())

    def test_double_close(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute("SELECT 1")
            yield from res.close()
            self.assertTrue(res.closed)
            self.assertIsNone(res.cursor)
            yield from res.close()
            self.assertTrue(res.closed)
            self.assertIsNone(res.cursor)

        self.loop.run_until_complete(go())

    @unittest.skip("Find out how to close cursor on __del__ method")
    def test_weakrefs(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            self.assertEqual(0, len(conn._weak_results))
            res = yield from conn.execute("SELECT 1")
            self.assertEqual(1, len(conn._weak_results))
            cur = res.cursor
            self.assertFalse(cur.closed)
            # TODO: fix this, how close cursor if result was deleted
            # yield from cur.close()
            del res
            self.assertTrue(cur.closed)
            self.assertEqual(0, len(conn._weak_results))

        self.loop.run_until_complete(go())

    def test_fetchall(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert().values(name='second'))

            res = yield from conn.execute(tbl.select())
            rows = yield from res.fetchall()
            self.assertEqual(2, len(rows))
            self.assertTrue(res.closed)
            self.assertTrue(res.returns_rows)
            self.assertEqual([(1, 'first'), (2, 'second')], rows)

        self.loop.run_until_complete(go())

    def test_fetchall_closed(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert().values(name='second'))

            res = yield from conn.execute(tbl.select())
            yield from res.close()
            with self.assertRaises(sa.ResourceClosedError):
                yield from res.fetchall()

        self.loop.run_until_complete(go())

    def test_fetchall_not_returns_rows(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute(tbl.delete())
            with self.assertRaises(sa.ResourceClosedError):
                yield from res.fetchall()

        self.loop.run_until_complete(go())

    def test_fetchone_closed(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert().values(name='second'))

            res = yield from conn.execute(tbl.select())
            yield from res.close()
            with self.assertRaises(sa.ResourceClosedError):
                yield from res.fetchone()

        self.loop.run_until_complete(go())

    def test_first_not_returns_rows(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute(tbl.delete())
            with self.assertRaises(sa.ResourceClosedError):
                yield from res.first()

        self.loop.run_until_complete(go())

    def test_fetchmany(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert().values(name='second'))

            res = yield from conn.execute(tbl.select())
            rows = yield from res.fetchmany()
            self.assertEqual(1, len(rows))
            self.assertFalse(res.closed)
            self.assertTrue(res.returns_rows)
            self.assertEqual([(1, 'first')], rows)

        self.loop.run_until_complete(go())

    def test_fetchmany_with_size(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert().values(name='second'))

            res = yield from conn.execute(tbl.select())
            rows = yield from res.fetchmany(100)
            self.assertEqual(2, len(rows))
            self.assertFalse(res.closed)
            self.assertTrue(res.returns_rows)
            self.assertEqual([(1, 'first'), (2, 'second')], rows)

        self.loop.run_until_complete(go())

    def test_fetchmany_closed(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert().values(name='second'))

            res = yield from conn.execute(tbl.select())
            yield from res.close()
            with self.assertRaises(sa.ResourceClosedError):
                yield from res.fetchmany()

        self.loop.run_until_complete(go())

    def test_fetchmany_with_size_closed(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            yield from conn.execute(tbl.insert().values(name='second'))

            res = yield from conn.execute(tbl.select())
            yield from res.close()
            with self.assertRaises(sa.ResourceClosedError):
                yield from res.fetchmany(5555)

        self.loop.run_until_complete(go())

    def test_fetchmany_not_returns_rows(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute(tbl.delete())
            with self.assertRaises(sa.ResourceClosedError):
                yield from res.fetchmany()

        self.loop.run_until_complete(go())

    def test_fetchmany_close_after_last_read(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()

            res = yield from conn.execute(tbl.select())
            rows = yield from res.fetchmany()
            self.assertEqual(1, len(rows))
            self.assertFalse(res.closed)
            self.assertTrue(res.returns_rows)
            self.assertEqual([(1, 'first')], rows)
            rows2 = yield from res.fetchmany()
            self.assertEqual(0, len(rows2))
            self.assertTrue(res.closed)

        self.loop.run_until_complete(go())

    def test_create_table(self, **kwargs):
        @asyncio.coroutine
        def go():
            conn = yield from self.connect()
            res = yield from conn.execute(DropTable(tbl))
            with self.assertRaises(sa.ResourceClosedError):
                yield from res.fetchmany()

            with self.assertRaises(aiomysql.ProgrammingError):
                yield from conn.execute("SELECT * FROM sa_tbl")

            res = yield from conn.execute(CreateTable(tbl))
            with self.assertRaises(sa.ResourceClosedError):
                yield from res.fetchmany()

            res = yield from conn.execute("SELECT * FROM sa_tbl")
            self.assertEqual(0, len(list(res)))

        self.loop.run_until_complete(go())
