import asyncio
from aiomysql import sa
# from aiomysql.connection import TIMEOUT

import os
import unittest

from sqlalchemy import MetaData, Table, Column, Integer, String

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


class TestEngine(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', '')
        self.engine = self.loop.run_until_complete(self.make_engine())
        self.loop.run_until_complete(self.start())

    def tearDown(self):
        self.engine.terminate()
        self.loop.run_until_complete(self.engine.wait_closed())
        self.loop.close()

    @asyncio.coroutine
    def make_engine(self, use_loop=True, **kwargs):
        if use_loop:
            return (yield from sa.create_engine(db=self.db,
                                                user=self.user,
                                                password=self.password,
                                                host=self.host,
                                                loop=self.loop,
                                                **kwargs))
        else:
            return (yield from sa.create_engine(db=self.db,
                                                user=self.user,
                                                password=self.password,
                                                host=self.host,
                                                **kwargs))

    @asyncio.coroutine
    def start(self):
        with (yield from self.engine) as conn:
            yield from conn.execute("DROP TABLE IF EXISTS sa_tbl3")
            yield from conn.execute("CREATE TABLE sa_tbl3 "
                                    "(id serial, name varchar(255))")

    def test_dialect(self):
        self.assertEqual(sa.engine._dialect, self.engine.dialect)

    def test_name(self):
        self.assertEqual('mysql', self.engine.name)

    def test_driver(self):
        self.assertEqual('pymysql', self.engine.driver)

    # def test_dsn(self):
    #     self.assertEqual(
    #         'dbname=aiomysql user=aiomysql password=xxxxxx host=127.0.0.1',
    #         self.engine.dsn)

    def test_minsize(self):
        self.assertEqual(10, self.engine.minsize)

    def test_maxsize(self):
        self.assertEqual(10, self.engine.maxsize)

    def test_size(self):
        self.assertEqual(10, self.engine.size)

    def test_freesize(self):
        self.assertEqual(10, self.engine.freesize)

    def test_make_engine_with_default_loop(self):

        @asyncio.coroutine
        def go():
            engine = yield from self.make_engine(use_loop=False)
            engine.close()
            yield from engine.wait_closed()

        asyncio.set_event_loop(self.loop)
        try:
            self.loop.run_until_complete(go())
        finally:
            asyncio.set_event_loop(None)

    def test_not_context_manager(self):
        @asyncio.coroutine
        def go():
            with self.assertRaises(RuntimeError):
                with self.engine:
                    pass
        self.loop.run_until_complete(go())

    def test_release_transacted(self):
        @asyncio.coroutine
        def go():
            conn = yield from self.engine.acquire()
            tr = yield from conn.begin()
            with self.assertRaises(sa.InvalidRequestError):
                self.engine.release(conn)
            del tr
        self.loop.run_until_complete(go())

    # def test_timeout(self):
    #     self.assertEqual(TIMEOUT, self.engine.timeout)

    # def test_timeout_override(self):
    #     @asyncio.coroutine
    #     def go():
    #         timeout = 1
    #         engine = yield from self.make_engine(timeout=timeout)
    #         self.assertEqual(timeout, engine.timeout)
    #         conn = yield from engine.acquire()
    #         with self.assertRaises(asyncio.TimeoutError):
    #             yield from conn.execute("SELECT pg_sleep(10)")
    #     self.loop.run_until_complete(go())

    def test_cannot_acquire_after_closing(self):
        @asyncio.coroutine
        def go():
            engine = yield from self.make_engine()
            engine.close()

            with self.assertRaises(RuntimeError):
                yield from engine.acquire()
            yield from engine.wait_closed()
        self.loop.run_until_complete(go())

    def test_wait_closed(self):
        @asyncio.coroutine
        def go():
            engine = yield from self.make_engine()

            c1 = yield from engine.acquire()
            c2 = yield from engine.acquire()
            self.assertEqual(10, engine.size)
            self.assertEqual(8, engine.freesize)

            ops = []

            @asyncio.coroutine
            def do_release(conn):
                yield from asyncio.sleep(0, loop=self.loop)
                engine.release(conn)
                ops.append('release')

            @asyncio.coroutine
            def wait_closed():
                yield from engine.wait_closed()
                ops.append('wait_closed')

            engine.close()
            yield from asyncio.gather(wait_closed(),
                                      do_release(c1),
                                      do_release(c2),
                                      loop=self.loop)
            self.assertEqual(['release', 'release', 'wait_closed'], ops)
            self.assertEqual(0, engine.freesize)
            engine.close()
            yield from engine.wait_closed()

        self.loop.run_until_complete(go())

    def test_terminate_with_acquired_connections(self):

        @asyncio.coroutine
        def go():
            engine = yield from self.make_engine()
            conn = yield from engine.acquire()
            engine.terminate()
            yield from engine.wait_closed()

            self.assertTrue(conn.closed)

        self.loop.run_until_complete(go())
