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

from core.data.schema import BaseData, metadata
from core.config import CoreConfig

profile = Table(
    "idac_profile",
    metadata,
    Column("id", Integer, primary_key=True, nullable=False),
    Column(
        "user",
        ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
        nullable=False,
    ),
    Column("version", Integer, nullable=False),
    Column("username", String(8)),
    Column("country", Integer),
    Column("store", Integer),
    Column("team_id", Integer, server_default="0"),
    Column("total_play", Integer, server_default="0"),
    Column("daily_play", Integer, server_default="0"),
    Column("day_play", Integer, server_default="0"),
    Column("mileage", Integer, server_default="0"),
    Column("asset_version", Integer, server_default="1"),
    Column("last_play_date", TIMESTAMP, server_default=func.now()),
    Column("mytitle_id", Integer, server_default="0"),
    Column("mytitle_efffect_id", Integer, server_default="0"),
    Column("sticker_id", Integer, server_default="0"),
    Column("sticker_effect_id", Integer, server_default="0"),
    Column("papercup_id", Integer, server_default="0"),
    Column("tachometer_id", Integer, server_default="0"),
    Column("aura_id", Integer, server_default="0"),
    Column("aura_color_id", Integer, server_default="0"),
    Column("aura_line_id", Integer, server_default="0"),
    Column("bgm_id", Integer, server_default="0"),
    Column("keyholder_id", Integer, server_default="0"),
    Column("start_menu_bg_id", Integer, server_default="0"),
    Column("use_car_id", Integer, server_default="1"),
    Column("use_style_car_id", Integer, server_default="1"),
    Column("bothwin_count", Integer, server_default="0"),
    Column("bothwin_score", Integer, server_default="0"),
    Column("subcard_count", Integer, server_default="0"),
    Column("vs_history", Integer, server_default="0"),
    Column("stamp_key_assign_0", Integer),
    Column("stamp_key_assign_1", Integer),
    Column("stamp_key_assign_2", Integer),
    Column("stamp_key_assign_3", Integer),
    Column("name_change_category", Integer, server_default="0"),
    Column("factory_disp", Integer, server_default="0"),
    Column("create_date", TIMESTAMP, server_default=func.now()),
    Column("cash", Integer, server_default="0"),
    Column("dressup_point", Integer, server_default="0"),
    Column("avatar_point", Integer, server_default="0"),
    Column("total_cash", Integer, server_default="0"),
    UniqueConstraint("user", "version", name="idac_profile_uk"),
    mysql_charset="utf8mb4",
)

# No point setting defaults since the game sends everything on profile creation anyway
config = Table(
    "idac_profile_config",
    metadata,
    Column("id", Integer, primary_key=True, nullable=False),
    Column(
        "user",
        ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
        nullable=False,
    ),
    Column("config_id", Integer),
    Column("steering_intensity", Integer),
    Column("transmission_type", Integer),
    Column("default_viewpoint", Integer),
    Column("favorite_bgm", Integer),
    Column("bgm_volume", Integer),
    Column("se_volume", Integer),
    Column("master_volume", Integer),
    Column("store_battle_policy", Integer),
    Column("battle_onomatope_display", Integer),
    Column("cornering_guide", Integer),
    Column("minimap", Integer),
    Column("line_guide", Integer),
    Column("ghost", Integer),
    Column("race_exit", Integer),
    Column("result_skip", Integer),
    Column("stamp_select_skip", Integer),
    UniqueConstraint("user", name="idac_profile_config_uk"),
    mysql_charset="utf8mb4",
)

# No point setting defaults since the game sends everything on profile creation anyway
avatar = Table(
    "idac_profile_avatar",
    metadata,
    Column("id", Integer, primary_key=True, nullable=False),
    Column(
        "user",
        ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
        nullable=False,
    ),
    Column("sex", Integer),
    Column("face", Integer),
    Column("eye", Integer),
    Column("mouth", Integer),
    Column("hair", Integer),
    Column("glasses", Integer),
    Column("face_accessory", Integer),
    Column("body", Integer),
    Column("body_accessory", Integer),
    Column("behind", Integer),
    Column("bg", Integer),
    Column("effect", Integer),
    Column("special", Integer),
    UniqueConstraint("user", name="idac_profile_avatar_uk"),
    mysql_charset="utf8mb4",
)

rank = Table(
    "idac_profile_rank",
    metadata,
    Column("id", Integer, primary_key=True, nullable=False),
    Column(
        "user",
        ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
        nullable=False,
    ),
    Column("version", Integer, nullable=False),
    Column("story_rank_exp", Integer, server_default="0"),
    Column("story_rank", Integer, server_default="1"),
    Column("time_trial_rank_exp", Integer, server_default="0"),
    Column("time_trial_rank", Integer, server_default="1"),
    Column("online_battle_rank_exp", Integer, server_default="0"),
    Column("online_battle_rank", Integer, server_default="1"),
    Column("store_battle_rank_exp", Integer, server_default="0"),
    Column("store_battle_rank", Integer, server_default="1"),
    Column("theory_exp", Integer, server_default="0"),
    Column("theory_rank", Integer, server_default="1"),
    Column("pride_group_id", Integer, server_default="0"),
    Column("pride_point", Integer, server_default="0"),
    Column("grade_exp", Integer, server_default="0"),
    Column("grade", Integer, server_default="1"),
    Column("grade_reward_dist", Integer, server_default="0"),
    Column("story_rank_reward_dist", Integer, server_default="0"),
    Column("time_trial_rank_reward_dist", Integer, server_default="0"),
    Column("online_battle_rank_reward_dist", Integer, server_default="0"),
    Column("store_battle_rank_reward_dist", Integer, server_default="0"),
    Column("theory_rank_reward_dist", Integer, server_default="0"),
    Column("max_attained_online_battle_rank", Integer, server_default="1"),
    Column("max_attained_pride_point", Integer, server_default="0"),
    Column("is_last_max", Integer, server_default="0"),
    UniqueConstraint("user", "version", name="idac_profile_rank_uk"),
    mysql_charset="utf8mb4",
)

stock = Table(
    "idac_profile_stock",
    metadata,
    Column("id", Integer, primary_key=True, nullable=False),
    Column(
        "user",
        ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
        nullable=False,
    ),
    Column("version", Integer, nullable=False),
    Column("mytitle_list", String(1024), server_default=""),
    Column("mytitle_new_list", String(1024), server_default=""),
    Column("avatar_face_list", String(255), server_default=""),
    Column("avatar_face_new_list", String(255), server_default=""),
    Column("avatar_eye_list", String(255), server_default=""),
    Column("avatar_eye_new_list", String(255), server_default=""),
    Column("avatar_hair_list", String(255), server_default=""),
    Column("avatar_hair_new_list", String(255), server_default=""),
    Column("avatar_body_list", String(255), server_default=""),
    Column("avatar_body_new_list", String(255), server_default=""),
    Column("avatar_mouth_list", String(255), server_default=""),
    Column("avatar_mouth_new_list", String(255), server_default=""),
    Column("avatar_glasses_list", String(255), server_default=""),
    Column("avatar_glasses_new_list", String(255), server_default=""),
    Column("avatar_face_accessory_list", String(255), server_default=""),
    Column("avatar_face_accessory_new_list", String(255), server_default=""),
    Column("avatar_body_accessory_list", String(255), server_default=""),
    Column("avatar_body_accessory_new_list", String(255), server_default=""),
    Column("avatar_behind_list", String(255), server_default=""),
    Column("avatar_behind_new_list", String(255), server_default=""),
    Column("avatar_bg_list", String(255), server_default=""),
    Column("avatar_bg_new_list", String(255), server_default=""),
    Column("avatar_effect_list", String(255), server_default=""),
    Column("avatar_effect_new_list", String(255), server_default=""),
    Column("avatar_special_list", String(255), server_default=""),
    Column("avatar_special_new_list", String(255), server_default=""),
    Column("stamp_list", String(255), server_default=""),
    Column("stamp_new_list", String(255), server_default=""),
    Column("keyholder_list", String(256), server_default=""),
    Column("keyholder_new_list", String(256), server_default=""),
    Column("papercup_list", String(255), server_default=""),
    Column("papercup_new_list", String(255), server_default=""),
    Column("tachometer_list", String(255), server_default=""),
    Column("tachometer_new_list", String(255), server_default=""),
    Column("aura_list", String(255), server_default=""),
    Column("aura_new_list", String(255), server_default=""),
    Column("aura_color_list", String(255), server_default=""),
    Column("aura_color_new_list", String(255), server_default=""),
    Column("aura_line_list", String(255), server_default=""),
    Column("aura_line_new_list", String(255), server_default=""),
    Column("bgm_list", String(255), server_default=""),
    Column("bgm_new_list", String(255), server_default=""),
    Column("dx_color_list", String(255), server_default=""),
    Column("dx_color_new_list", String(255), server_default=""),
    Column("start_menu_bg_list", String(255), server_default=""),
    Column("start_menu_bg_new_list", String(255), server_default=""),
    Column("under_neon_list", String(255), server_default=""),
    UniqueConstraint("user", "version", name="idac_profile_stock_uk"),
    mysql_charset="utf8mb4",
)

theory = Table(
    "idac_profile_theory",
    metadata,
    Column("id", Integer, primary_key=True, nullable=False),
    Column(
        "user",
        ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
        nullable=False,
    ),
    Column("version", Integer, nullable=False),
    Column("play_count", Integer, server_default="0"),
    Column("play_count_multi", Integer, server_default="0"),
    Column("partner_id", Integer),
    Column("partner_progress", Integer),
    Column("partner_progress_score", Integer),
    Column("practice_start_rank", Integer, server_default="0"),
    Column("general_flag", Integer, server_default="0"),
    Column("vs_history", Integer, server_default="0"),
    Column("vs_history_multi", Integer, server_default="0"),
    Column("win_count", Integer, server_default="0"),
    Column("win_count_multi", Integer, server_default="0"),
    UniqueConstraint("user", "version", name="idac_profile_theory_uk"),
    mysql_charset="utf8mb4",
)


class IDACProfileData(BaseData):
    def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
        super().__init__(cfg, conn)
        self.date_time_format_ext = (
            "%Y-%m-%d %H:%M:%S.%f"  # needs to be lopped off at [:-5]
        )
        self.date_time_format_short = "%Y-%m-%d"

    async def get_profile(self, aime_id: int, version: int) -> Optional[Row]:
        sql = select(profile).where(
            and_(
                profile.c.user == aime_id,
                profile.c.version == version,
            )
        )

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

    async def get_different_random_profiles(
        self, aime_id: int, version: int, count: int = 9
    ) -> Optional[Row]:
        sql = (
            select(profile)
            .where(
                and_(
                    profile.c.user != aime_id,
                    profile.c.version == version,
                )
            )
            .order_by(func.rand())
            .limit(count)
        )

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

    async def get_profile_config(self, aime_id: int) -> Optional[Row]:
        sql = select(config).where(
            and_(
                config.c.user == aime_id,
            )
        )

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

    async def get_profile_avatar(self, aime_id: int) -> Optional[Row]:
        sql = select(avatar).where(
            and_(
                avatar.c.user == aime_id,
            )
        )

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

    async def get_profile_rank(self, aime_id: int, version: int) -> Optional[Row]:
        sql = select(rank).where(
            and_(
                rank.c.user == aime_id,
                rank.c.version == version,
            )
        )

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

    async def get_profile_stock(self, aime_id: int, version: int) -> Optional[Row]:
        sql = select(stock).where(
            and_(
                stock.c.user == aime_id,
                stock.c.version == version,
            )
        )

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

    async def get_profile_theory(self, aime_id: int, version: int) -> Optional[Row]:
        sql = select(theory).where(
            and_(
                theory.c.user == aime_id,
                theory.c.version == version,
            )
        )

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

    async def put_profile(
        self, aime_id: int, version: int, profile_data: Dict
    ) -> Optional[int]:
        profile_data["user"] = aime_id
        profile_data["version"] = version

        sql = insert(profile).values(**profile_data)
        conflict = sql.on_duplicate_key_update(**profile_data)
        result = await self.execute(conflict)

        if result is None:
            self.logger.warning(f"put_profile: Failed to update! aime_id: {aime_id}")
            return None
        return result.lastrowid

    async def put_profile_config(self, aime_id: int, config_data: Dict) -> Optional[int]:
        config_data["user"] = aime_id

        sql = insert(config).values(**config_data)
        conflict = sql.on_duplicate_key_update(**config_data)
        result = await self.execute(conflict)

        if result is None:
            self.logger.warning(
                f"put_profile_config: Failed to update! aime_id: {aime_id}"
            )
            return None
        return result.lastrowid

    async def put_profile_avatar(self, aime_id: int, avatar_data: Dict) -> Optional[int]:
        avatar_data["user"] = aime_id

        sql = insert(avatar).values(**avatar_data)
        conflict = sql.on_duplicate_key_update(**avatar_data)
        result = await self.execute(conflict)

        if result is None:
            self.logger.warning(
                f"put_profile_avatar: Failed to update! aime_id: {aime_id}"
            )
            return None
        return result.lastrowid

    async def put_profile_rank(
        self, aime_id: int, version: int, rank_data: Dict
    ) -> Optional[int]:
        rank_data["user"] = aime_id
        rank_data["version"] = version

        sql = insert(rank).values(**rank_data)
        conflict = sql.on_duplicate_key_update(**rank_data)
        result = await self.execute(conflict)

        if result is None:
            self.logger.warning(f"put_profile_rank: Failed to update! aime_id: {aime_id}")
            return None
        return result.lastrowid

    async def put_profile_stock(
        self, aime_id: int, version: int, stock_data: Dict
    ) -> Optional[int]:
        stock_data["user"] = aime_id
        stock_data["version"] = version

        sql = insert(stock).values(**stock_data)
        conflict = sql.on_duplicate_key_update(**stock_data)
        result = await self.execute(conflict)

        if result is None:
            self.logger.warning(f"put_profile_stock: Failed to update! aime_id: {aime_id}")
            return None
        return result.lastrowid

    async def put_profile_theory(
        self, aime_id: int, version: int, theory_data: Dict
    ) -> Optional[int]:
        theory_data["user"] = aime_id
        theory_data["version"] = version

        sql = insert(theory).values(**theory_data)
        conflict = sql.on_duplicate_key_update(**theory_data)
        result = await self.execute(conflict)

        if result is None:
            self.logger.warning(
                f"put_profile_theory: Failed to update! aime_id: {aime_id}"
            )
            return None
        return result.lastrowid
