from __future__ import annotations

from typing import Optional, Tuple
from sqlalchemy import select, delete
from sqlalchemy.orm import selectinload
from sqlalchemy.ext.asyncio import AsyncSession

from src.database.models import User, UserProfile, Strategy, StrategyDayState, BotSettings
from src.database.enums import (
    Gender, PracticePlace, ClientsExperience,
    MassageTechnique, SocialSkill, CommunicationEase,
)

# ---------- utils ----------
def _to_enum(enum_cls, value):
    if value is None:
        return None
    if isinstance(value, enum_cls):
        return value
    try:
        return enum_cls(value)
    except Exception:
        raise ValueError(f"Invalid {enum_cls.__name__}: {value!r}")

async def get_user_by_tg(session: AsyncSession, tg_id: str) -> Optional[User]:
    """Fetch user by tg_id."""
    res = await session.execute(select(User).where(User.tg_id == tg_id))
    return res.scalar_one_or_none()


async def get_user_with_profile_by_tg(session: AsyncSession, tg_id: str) -> Optional[User]:
    """Fetch user with profile and bot settings by tg_id."""
    res = await session.execute(
        select(User)
        .options(selectinload(User.profile), selectinload(User.bot_settings))
        .where(User.tg_id == tg_id)
    )
    return res.scalar_one_or_none()

async def is_user_registered(session: AsyncSession, tg_id: str) -> bool:
    """Return True if user exists (by tg_id)."""
    return (await get_user_by_tg(session, tg_id)) is not None


async def register_user(
    session: AsyncSession,
    *,
    tg_id: str,
    user_name: Optional[str],
    user_gender: Optional[Gender | str],
    city: Optional[str],
    where_practicing: Optional[PracticePlace | str],
    have_clients: Optional[ClientsExperience | str],
    massage_technique: Optional[MassageTechnique | str],
    social_skill: Optional[SocialSkill | str],
    communication_ease: Optional[CommunicationEase | str],
    raw_json: Optional[dict] = None,
    raise_if_exists: bool = True,
) -> Tuple[User, bool]:
    """
    Create user + profile once. Does NOT update existing users.
    Returns (user, created_flag). Does not commit.
    """
    # existence check
    existing = await get_user_by_tg(session, tg_id)
    if existing:
        if raise_if_exists:
            raise ValueError(f"user with tg_id={tg_id} already registered")
        return existing, False

    # create user
    user = User(
        tg_id=tg_id,
        user_name=user_name or None,
        user_gender=_to_enum(Gender, user_gender),
        city=city or None,
    )
    session.add(user)
    await session.flush()

    # create profile
    profile = UserProfile(
        user_id=user.id,
        where_practicing=_to_enum(PracticePlace, where_practicing),
        have_clients=_to_enum(ClientsExperience, have_clients),
        massage_technique=_to_enum(MassageTechnique, massage_technique),
        social_skill=_to_enum(SocialSkill, social_skill),
        communication_ease=_to_enum(CommunicationEase, communication_ease),
        raw_json=raw_json or None,
    )
    session.add(profile)

    return user, True


async def get_all_user_tg_ids(session: AsyncSession) -> list[str]:
    """Get all user Telegram IDs from database."""
    result = await session.execute(select(User.tg_id))
    return [str(tg_id) for tg_id in result.scalars().all()]


async def delete_user_by_tg(session: AsyncSession, tg_id: str) -> bool:
    user_id: Optional[int] = await session.scalar(
        select(User.id).where(User.tg_id == tg_id)
    )
    if not user_id:
        return False

    # subquery со списком стратегий пользователя
    strat_ids_subq = select(Strategy.id).where(Strategy.user_id == user_id)

    # порядок важен: сначала day_states → strategies → profile → user
    await session.execute(
        delete(StrategyDayState).where(StrategyDayState.strategy_id.in_(strat_ids_subq))
    )
    await session.execute(delete(Strategy).where(Strategy.user_id == user_id))
    await session.execute(delete(UserProfile).where(UserProfile.user_id == user_id))
    await session.execute(delete(User).where(User.id == user_id))

    # flush/commit — снаружи (в роутере)
    return True


# ---------- bot settings ----------
async def get_bot_settings_by_tg(session: AsyncSession, tg_id: str) -> Optional[BotSettings]:
    """Fetch bot settings by user tg_id."""
    res = await session.execute(
        select(BotSettings)
        .join(User, BotSettings.user_id == User.id)
        .where(User.tg_id == tg_id)
    )
    return res.scalar_one_or_none()


async def create_or_update_bot_settings(
    session: AsyncSession,
    *,
    tg_id: str,
    bot_voice_name: Optional[str] = None,
    bot_voice_gender: Optional[Gender | str] = None,
) -> BotSettings:
    """
    Create or update bot settings for user.
    Returns bot settings. Does not commit.
    """
    # Get user
    user = await get_user_by_tg(session, tg_id)
    if not user:
        raise ValueError(f"User with tg_id={tg_id} not found")
    
    # Check if settings exist
    existing = await get_bot_settings_by_tg(session, tg_id)
    
    if existing:
        # Update existing
        if bot_voice_name is not None:
            existing.bot_voice_name = bot_voice_name
        if bot_voice_gender is not None:
            existing.bot_voice_gender = _to_enum(Gender, bot_voice_gender)
        return existing
    else:
        # Create new
        settings = BotSettings(
            user_id=user.id,
            bot_voice_name=bot_voice_name,
            bot_voice_gender=_to_enum(Gender, bot_voice_gender),
        )
        session.add(settings)
        return settings