from typing import Dict, List, Optional
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
from sqlalchemy.engine.base import Connection
from sqlalchemy.engine import Row
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.dialects.mysql import insert

from core.data.schema import BaseData, metadata

music = Table(
    "diva_static_music",
    metadata,
    Column("id", Integer, primary_key=True, nullable=False),
    Column("version", Integer, nullable=False),
    Column("songId", Integer),
    Column("chartId", Integer),
    Column("title", String(255)),
    Column("vocaloid_arranger", String(255)),
    Column("pv_illustrator", String(255)),
    Column("lyrics", String(255)),
    Column("bg_music", String(255)),
    Column("level", Float),
    Column("bpm", Integer),
    Column("date", String(255)),
    UniqueConstraint("version", "songId", "chartId", name="diva_static_music_uk"),
    mysql_charset="utf8mb4",
)

quests = Table(
    "diva_static_quests",
    metadata,
    Column("id", Integer, primary_key=True, nullable=False),
    Column("version", Integer, nullable=False),
    Column("questId", Integer),
    Column("name", String(255)),
    Column("quest_enable", Boolean, server_default="1"),
    Column("kind", Integer),
    Column("unknown_0", Integer),
    Column("unknown_1", Integer),
    Column("unknown_2", Integer),
    Column("quest_order", Integer),
    Column("start_datetime", String(255)),
    Column("end_datetime", String(255)),
    UniqueConstraint("version", "questId", name="diva_static_quests_uk"),
    mysql_charset="utf8mb4",
)

shop = Table(
    "diva_static_shop",
    metadata,
    Column("id", Integer, primary_key=True, nullable=False),
    Column("version", Integer, nullable=False),
    Column("shopId", Integer),
    Column("name", String(255)),
    Column("type", Integer),
    Column("points", Integer),
    Column("unknown_0", Integer),
    Column("start_date", String(255)),
    Column("end_date", String(255)),
    Column("enabled", Boolean, server_default="1"),
    UniqueConstraint("version", "shopId", name="diva_static_shop_uk"),
    mysql_charset="utf8mb4",
)

items = Table(
    "diva_static_items",
    metadata,
    Column("id", Integer, primary_key=True, nullable=False),
    Column("version", Integer, nullable=False),
    Column("itemId", Integer),
    Column("name", String(255)),
    Column("type", Integer),
    Column("points", Integer),
    Column("unknown_0", Integer),
    Column("start_date", String(255)),
    Column("end_date", String(255)),
    Column("enabled", Boolean, server_default="1"),
    UniqueConstraint("version", "itemId", name="diva_static_items_uk"),
    mysql_charset="utf8mb4",
)


class DivaStaticData(BaseData):
    async def put_quests(
        self,
        version: int,
        questId: int,
        name: str,
        kind: int,
        unknown_0: int,
        unknown_1: int,
        unknown_2: int,
        quest_order: int,
        start_datetime: str,
        end_datetime: str,
    ) -> Optional[int]:
        sql = insert(quests).values(
            version=version,
            questId=questId,
            name=name,
            kind=kind,
            unknown_0=unknown_0,
            unknown_1=unknown_1,
            unknown_2=unknown_2,
            quest_order=quest_order,
            start_datetime=start_datetime,
            end_datetime=end_datetime,
        )

        conflict = sql.on_duplicate_key_update(name=name)

        result = await self.execute(conflict)
        if result is None:
            return None
        return result.lastrowid

    async def get_enabled_quests(self, version: int) -> Optional[List[Row]]:
        sql = select(quests).where(
            and_(quests.c.version == version, quests.c.quest_enable == True)
        )

        result = await self.execute(sql)
        if result is None:
            return None
        return result.fetchall()

    async def put_shop(
        self,
        version: int,
        shopId: int,
        name: str,
        type: int,
        points: int,
        unknown_0: int,
        start_date: str,
        end_date: str,
    ) -> Optional[int]:
        sql = insert(shop).values(
            version=version,
            shopId=shopId,
            name=name,
            type=type,
            points=points,
            unknown_0=unknown_0,
            start_date=start_date,
            end_date=end_date,
        )

        conflict = sql.on_duplicate_key_update(name=name)

        result = await self.execute(conflict)
        if result is None:
            return None
        return result.lastrowid

    async def get_enabled_shop(self, version: int, shopId: int) -> Optional[Row]:
        sql = select(shop).where(
            and_(
                shop.c.version == version,
                shop.c.shopId == shopId,
                shop.c.enabled == True,
            )
        )

        result = await self.execute(sql)
        if result is None:
            return None
        return result.fetchone()

    async def get_enabled_shops(self, version: int) -> Optional[List[Row]]:
        sql = select(shop).where(
            and_(shop.c.version == version, shop.c.enabled == True)
        )

        result = await self.execute(sql)
        if result is None:
            return None
        return result.fetchall()

    async def put_items(
        self,
        version: int,
        itemId: int,
        name: str,
        type: int,
        points: int,
        unknown_0: int,
        start_date: str,
        end_date: str,
    ) -> Optional[int]:
        sql = insert(items).values(
            version=version,
            itemId=itemId,
            name=name,
            type=type,
            points=points,
            unknown_0=unknown_0,
            start_date=start_date,
            end_date=end_date,
        )

        conflict = sql.on_duplicate_key_update(name=name)

        result = await self.execute(conflict)
        if result is None:
            return None
        return result.lastrowid

    async def get_enabled_item(self, version: int, itemId: int) -> Optional[Row]:
        sql = select(items).where(
            and_(
                items.c.version == version,
                items.c.itemId == itemId,
                items.c.enabled == True,
            )
        )

        result = await self.execute(sql)
        if result is None:
            return None
        return result.fetchone()

    async def get_enabled_items(self, version: int) -> Optional[List[Row]]:
        sql = select(items).where(
            and_(items.c.version == version, items.c.enabled == True)
        )

        result = await self.execute(sql)
        if result is None:
            return None
        return result.fetchall()

    async def put_music(
        self,
        version: int,
        song: int,
        chart: int,
        title: str,
        arranger: str,
        illustrator: str,
        lyrics: str,
        music_comp: str,
        level: float,
        bpm: int,
        date: str,
    ) -> Optional[int]:
        sql = insert(music).values(
            version=version,
            songId=song,
            chartId=chart,
            title=title,
            vocaloid_arranger=arranger,
            pv_illustrator=illustrator,
            lyrics=lyrics,
            bg_music=music_comp,
            level=level,
            bpm=bpm,
            date=date,
        )

        conflict = sql.on_duplicate_key_update(
            title=title,
            vocaloid_arranger=arranger,
            pv_illustrator=illustrator,
            lyrics=lyrics,
            bg_music=music_comp,
            level=level,
            bpm=bpm,
            date=date,
        )

        result = await self.execute(conflict)
        if result is None:
            return None
        return result.lastrowid

    async def get_music(
        self, version: int, song_id: Optional[int] = None
    ) -> Optional[List[Row]]:
        if song_id is None:
            sql = select(music).where(music.c.version == version)
        else:
            sql = select(music).where(
                and_(
                    music.c.version == version,
                    music.c.songId == song_id,
                )
            )

        result = await self.execute(sql)
        if result is None:
            return None
        return result.fetchall()

    async def get_music_chart(
        self, version: int, song_id: int, chart_id: int
    ) -> Optional[List[Row]]:
        sql = select(music).where(
            and_(
                music.c.version == version,
                music.c.songId == song_id,
                music.c.chartId == chart_id,
            )
        )

        result = await self.execute(sql)
        if result is None:
            return None
        return result.fetchone()
