import datetime
from decimal import Decimal
from enum import Enum

import pytest

from asyncmy.cursors import DictCursor, RE_INSERT_VALUES


@pytest.mark.asyncio
async def test_fetchone(connection):
    async with connection.cursor() as cursor:
        await cursor.execute("SELECT 1")
        ret = await cursor.fetchone()
        assert ret == (1,)


@pytest.mark.asyncio
async def test_fetchall(connection):
    async with connection.cursor() as cursor:
        await cursor.execute("SELECT 1")
        ret = await cursor.fetchall()
        assert ret == ((1,),)


@pytest.mark.asyncio
async def test_dict_cursor(connection):
    async with connection.cursor(cursor=DictCursor) as cursor:
        await cursor.execute("SELECT 1")
        ret = await cursor.fetchall()
        assert ret == [{"1": 1}]


@pytest.mark.asyncio
async def test_insert(connection):
    async with connection.cursor(cursor=DictCursor) as cursor:
        rows = await cursor.execute(
            """INSERT INTO test.asyncmy(id,`decimal`, date, datetime, time, 
            `float`, string, `tinyint`) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)""",
            (
                1,
                1,
                "2020-08-08",
                "2020-08-08 00:00:00",
                "00:00:00",
                1,
                "1",
                1,
            ),
        )
        assert rows == 1
        await cursor.execute("select * from test.asyncmy where id = %s", (cursor.lastrowid,))
        result = await cursor.fetchall()
        assert result == [
            {
                "id": 1,
                "decimal": Decimal("1.00"),
                "date": datetime.date(2020, 8, 8),
                "datetime": datetime.datetime(2020, 8, 8, 0, 0),
                "time": datetime.timedelta(hours=0, minutes=0, seconds=0),
                "float": 1.0,
                "string": "1",
                "tinyint": 1,
            }
        ]


@pytest.mark.asyncio
async def test_delete(connection):
    async with connection.cursor() as cursor:
        rows = await cursor.execute("delete from test.asyncmy where id = -1")
        assert rows == 0


@pytest.mark.asyncio
async def test_executemany(connection):
    async with connection.cursor(cursor=DictCursor) as cursor:
        rows = await cursor.executemany(
            """INSERT INTO test.asyncmy(`decimal`, date, datetime, time, `float`, 
            string, `tinyint`) VALUES (%s,%s,%s,%s,%s,%s,%s)""",
            [
                (
                    1,
                    "2020-08-08",
                    "2020-08-08 00:00:00",
                    "00:00:00",
                    1,
                    "1",
                    1,
                ),
                (
                    1,
                    "2020-08-08",
                    "2020-08-08 00:00:00",
                    "00:00:00",
                    1,
                    "1",
                    1,
                ),
            ],
        )
        assert rows == 2


@pytest.mark.asyncio
async def test_table_ddl(connection):
    async with connection.cursor() as cursor:
        await cursor.execute("drop table if exists test.alter_table")
        create_table_sql = """
            CREATE TABLE test.alter_table
(
    `id` int primary key auto_increment
)
            """
        await cursor.execute(create_table_sql)
        add_column_sql = "alter table test.alter_table add column c varchar(20)"
        await cursor.execute(add_column_sql)
        await cursor.execute("drop table test.alter_table")


class EnumValue(str, Enum):
    VALUE = "1"


@pytest.mark.asyncio
async def test_insert_enum(connection):
    async with connection.cursor(cursor=DictCursor) as cursor:
        rows = await cursor.execute(
            """INSERT INTO test.asyncmy(id, `decimal`, date, datetime, time,
             `float`, string, `tinyint`) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)""",
            (
                -1,
                1,
                "2020-08-08",
                "2020-08-08 00:00:00",
                "00:00:00",
                1,
                EnumValue.VALUE,
                1,
            ),
        )
        assert rows == 1


def test_executemany_regex():
    query = "INSERT INTO foo (bar, baz) VALUES (%s, %s)"
    match = RE_INSERT_VALUES.match(query)
    assert match is not None
    assert match.group(1) == "INSERT INTO foo (bar, baz) VALUES "
    assert match.group(2) == "(%s, %s)"
    assert match.group(3) == ""

    # Deprecated VALUES syntax
    query = "INSERT INTO foo (bar, baz) VALUES (%s, %s) ON DUPLICATE KEY UPDATE baz=VALUES(baz)"
    match = RE_INSERT_VALUES.match(query)
    assert match is not None
    assert match.group(1) == "INSERT INTO foo (bar, baz) VALUES "
    assert match.group(2) == "(%s, %s)"
    assert match.group(3) == " ON DUPLICATE KEY UPDATE baz=VALUES(baz)"

    # The new syntax
    query = "INSERT INTO foo (bar, baz) VALUES (%s, %s) AS new ON DUPLICATE KEY UPDATE baz=new.baz"
    match = RE_INSERT_VALUES.match(query)
    assert match is not None
    assert match.group(1) == "INSERT INTO foo (bar, baz) VALUES "
    assert match.group(2) == "(%s, %s)"
    assert match.group(3) == " AS new ON DUPLICATE KEY UPDATE baz=new.baz"

    # Test REPLACE VALUES
    query = "REPLACE INTO foo (bar, baz, aa, bb) VALUES (%s, %s, %s, %s)"
    match = RE_INSERT_VALUES.match(query)
    assert match is not None
    assert match.group(1) == "REPLACE INTO foo (bar, baz, aa, bb) VALUES "
    assert match.group(2) == "(%s, %s, %s, %s)"
    assert match.group(3) == ""

    # Test invalid queries
    query = "INSERT INTO foo (bar, baz) VALUES (%s, %s), (%s, %s)"
    match = RE_INSERT_VALUES.match(query)
    assert match is None


@pytest.mark.asyncio
async def test_cursor_is_awaitable(connection):
    """aiomysql's conn.cursor() is a coroutine; `await` must work here too."""
    cursor = await connection.cursor()
    await cursor.execute("SELECT 1")
    assert await cursor.fetchone() == (1,)
    await cursor.close()


@pytest.mark.asyncio
async def test_awaited_cursor_honours_cursor_class(connection):
    cursor = await connection.cursor(DictCursor)
    await cursor.execute("SELECT 1 AS one")
    assert await cursor.fetchone() == {"one": 1}
    await cursor.close()
