"""
Shop Service - handles shop items, purchases, and inventory management
"""
from datetime import datetime
from fastapi import HTTPException
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.models import User, ShopItem, UserInventory, ShopItemType
from app.services.coins import coins_service


class ShopService:
    """Service for shop operations"""

    async def get_available_items(
        self,
        db: AsyncSession,
        item_type: str | None = None,
        include_unavailable: bool = False,
    ) -> list[ShopItem]:
        """
        Get list of shop items.

        Args:
            item_type: Filter by item type (frame, title, etc.)
            include_unavailable: Include inactive/out of stock items
        """
        query = select(ShopItem)

        if item_type:
            query = query.where(ShopItem.item_type == item_type)

        if not include_unavailable:
            now = datetime.utcnow()
            query = query.where(
                ShopItem.is_active == True,
                (ShopItem.available_from.is_(None)) | (ShopItem.available_from <= now),
                (ShopItem.available_until.is_(None)) | (ShopItem.available_until >= now),
                (ShopItem.stock_remaining.is_(None)) | (ShopItem.stock_remaining > 0),
            )

        query = query.order_by(ShopItem.price.asc())
        result = await db.execute(query)
        return list(result.scalars().all())

    async def get_item_by_id(self, db: AsyncSession, item_id: int) -> ShopItem | None:
        """Get shop item by ID"""
        result = await db.execute(select(ShopItem).where(ShopItem.id == item_id))
        return result.scalar_one_or_none()

    async def get_item_by_code(self, db: AsyncSession, code: str) -> ShopItem | None:
        """Get shop item by code"""
        result = await db.execute(select(ShopItem).where(ShopItem.code == code))
        return result.scalar_one_or_none()

    async def purchase_item(
        self,
        db: AsyncSession,
        user: User,
        item_id: int,
        quantity: int = 1,
    ) -> tuple[UserInventory, int]:
        """
        Purchase an item from the shop.

        Args:
            user: The purchasing user
            item_id: ID of item to purchase
            quantity: Number to purchase (only for consumables)

        Returns:
            Tuple of (inventory item, total cost)

        Raises:
            HTTPException: If item not found, not available, or insufficient funds
        """
        # Get item
        item = await self.get_item_by_id(db, item_id)
        if not item:
            raise HTTPException(status_code=404, detail="Item not found")

        # Check availability
        if not item.is_available:
            raise HTTPException(status_code=400, detail="Item is not available")

        # For non-consumables, quantity is always 1
        if item.item_type != ShopItemType.CONSUMABLE.value:
            quantity = 1

            # Check if already owned
            existing = await db.execute(
                select(UserInventory).where(
                    UserInventory.user_id == user.id,
                    UserInventory.item_id == item.id,
                )
            )
            if existing.scalar_one_or_none():
                raise HTTPException(status_code=400, detail="You already own this item")

        # Check stock
        if item.stock_remaining is not None and item.stock_remaining < quantity:
            raise HTTPException(status_code=400, detail="Not enough stock available")

        # Calculate total cost
        total_cost = item.price * quantity

        # Check balance
        if user.coins_balance < total_cost:
            raise HTTPException(status_code=400, detail="Not enough coins")

        # Deduct coins
        success = await coins_service.spend_coins(
            db, user, total_cost,
            f"Purchase: {item.name} x{quantity}",
            "shop_item", item.id,
        )
        if not success:
            raise HTTPException(status_code=400, detail="Payment failed")

        # Add to inventory
        if item.item_type == ShopItemType.CONSUMABLE.value:
            # For consumables, increase quantity if already exists
            existing_result = await db.execute(
                select(UserInventory).where(
                    UserInventory.user_id == user.id,
                    UserInventory.item_id == item.id,
                )
            )
            inv_item = existing_result.scalar_one_or_none()

            if inv_item:
                inv_item.quantity += quantity
            else:
                inv_item = UserInventory(
                    user_id=user.id,
                    item_id=item.id,
                    quantity=quantity,
                )
                db.add(inv_item)
        else:
            # For cosmetics, create new inventory entry
            inv_item = UserInventory(
                user_id=user.id,
                item_id=item.id,
                quantity=1,
            )
            db.add(inv_item)

        # Decrease stock if limited
        if item.stock_remaining is not None:
            item.stock_remaining -= quantity

        await db.flush()
        return inv_item, total_cost

    async def get_user_inventory(
        self,
        db: AsyncSession,
        user_id: int,
        item_type: str | None = None,
    ) -> list[UserInventory]:
        """Get user's inventory"""
        query = (
            select(UserInventory)
            .options(selectinload(UserInventory.item))
            .where(UserInventory.user_id == user_id)
        )

        if item_type:
            query = query.join(ShopItem).where(ShopItem.item_type == item_type)

        # Exclude empty consumables
        query = query.where(UserInventory.quantity > 0)

        result = await db.execute(query)
        return list(result.scalars().all())

    async def get_inventory_item(
        self,
        db: AsyncSession,
        user_id: int,
        inventory_id: int,
    ) -> UserInventory | None:
        """Get specific inventory item"""
        result = await db.execute(
            select(UserInventory)
            .options(selectinload(UserInventory.item))
            .where(
                UserInventory.id == inventory_id,
                UserInventory.user_id == user_id,
            )
        )
        return result.scalar_one_or_none()

    async def equip_item(
        self,
        db: AsyncSession,
        user: User,
        inventory_id: int,
    ) -> ShopItem:
        """
        Equip a cosmetic item from inventory.

        Returns: The equipped item

        Raises:
            HTTPException: If item not found or is a consumable
        """
        # Get inventory item
        inv_item = await self.get_inventory_item(db, user.id, inventory_id)
        if not inv_item:
            raise HTTPException(status_code=404, detail="Item not found in inventory")

        item = inv_item.item

        if item.item_type == ShopItemType.CONSUMABLE.value:
            raise HTTPException(status_code=400, detail="Cannot equip consumables")

        # Unequip current item of same type
        await db.execute(
            update(UserInventory)
            .where(
                UserInventory.user_id == user.id,
                UserInventory.equipped == True,
                UserInventory.item_id.in_(
                    select(ShopItem.id).where(ShopItem.item_type == item.item_type)
                ),
            )
            .values(equipped=False)
        )

        # Equip new item
        inv_item.equipped = True

        # Update user's equipped_*_id
        if item.item_type == ShopItemType.FRAME.value:
            user.equipped_frame_id = item.id
        elif item.item_type == ShopItemType.TITLE.value:
            user.equipped_title_id = item.id
        elif item.item_type == ShopItemType.NAME_COLOR.value:
            user.equipped_name_color_id = item.id
        elif item.item_type == ShopItemType.BACKGROUND.value:
            user.equipped_background_id = item.id

        return item

    async def unequip_item(
        self,
        db: AsyncSession,
        user: User,
        item_type: str,
    ) -> None:
        """Unequip item of specified type"""
        # Unequip from inventory
        await db.execute(
            update(UserInventory)
            .where(
                UserInventory.user_id == user.id,
                UserInventory.equipped == True,
                UserInventory.item_id.in_(
                    select(ShopItem.id).where(ShopItem.item_type == item_type)
                ),
            )
            .values(equipped=False)
        )

        # Clear user's equipped_*_id
        if item_type == ShopItemType.FRAME.value:
            user.equipped_frame_id = None
        elif item_type == ShopItemType.TITLE.value:
            user.equipped_title_id = None
        elif item_type == ShopItemType.NAME_COLOR.value:
            user.equipped_name_color_id = None
        elif item_type == ShopItemType.BACKGROUND.value:
            user.equipped_background_id = None

    async def check_user_owns_item(
        self,
        db: AsyncSession,
        user_id: int,
        item_id: int,
    ) -> bool:
        """Check if user owns an item"""
        result = await db.execute(
            select(UserInventory).where(
                UserInventory.user_id == user_id,
                UserInventory.item_id == item_id,
                UserInventory.quantity > 0,
            )
        )
        return result.scalar_one_or_none() is not None


# Singleton instance
shop_service = ShopService()
