# app/crud/item.py
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy.orm import selectinload # Ensure selectinload is imported
from sqlalchemy import delete as sql_delete, update as sql_update # Use aliases
from sqlalchemy.exc import SQLAlchemyError, IntegrityError, OperationalError
from typing import Optional, List as PyList
from datetime import datetime, timezone
import logging # Add logging import
from sqlalchemy import func

from app.models import Item as ItemModel, User as UserModel # Import UserModel for type hints if needed for selectinload
from app.schemas.item import ItemCreate, ItemUpdate
from app.core.exceptions import (
    ItemNotFoundError,
    DatabaseConnectionError,
    DatabaseIntegrityError,
    DatabaseQueryError,
    DatabaseTransactionError,
    ConflictError,
    ItemOperationError # Add if specific item operation errors are needed
)

logger = logging.getLogger(__name__) # Initialize logger

async def create_item(db: AsyncSession, item_in: ItemCreate, list_id: int, user_id: int) -> ItemModel:
    """Creates a new item record for a specific list, setting its position."""
    try:
        async with db.begin_nested() if db.in_transaction() else db.begin() as transaction:
            # Get the current max position in the list
            max_pos_stmt = select(func.max(ItemModel.position)).where(ItemModel.list_id == list_id)
            max_pos_result = await db.execute(max_pos_stmt)
            max_pos = max_pos_result.scalar_one_or_none() or 0

            db_item = ItemModel(
                name=item_in.name,
                quantity=item_in.quantity,
                list_id=list_id,
                added_by_id=user_id,
                is_complete=False,
                position=max_pos + 1 # Set the new position
            )
            db.add(db_item)
            await db.flush() # Assigns ID

            # Re-fetch with relationships
            stmt = (
                select(ItemModel)
                .where(ItemModel.id == db_item.id)
                .options(
                    selectinload(ItemModel.added_by_user),
                    selectinload(ItemModel.completed_by_user) # Will be None but loads relationship
                )
            )
            result = await db.execute(stmt)
            loaded_item = result.scalar_one_or_none()

            if loaded_item is None:
                # await transaction.rollback() # Redundant, context manager handles rollback on exception
                raise ItemOperationError("Failed to load item after creation.") # Define ItemOperationError

            return loaded_item
    except IntegrityError as e:
        logger.error(f"Database integrity error during item creation: {str(e)}", exc_info=True)
        raise DatabaseIntegrityError(f"Failed to create item: {str(e)}")
    except OperationalError as e:
        logger.error(f"Database connection error during item creation: {str(e)}", exc_info=True)
        raise DatabaseConnectionError(f"Database connection error: {str(e)}")
    except SQLAlchemyError as e:
        logger.error(f"Unexpected SQLAlchemy error during item creation: {str(e)}", exc_info=True)
        raise DatabaseTransactionError(f"Failed to create item: {str(e)}")
    # Removed generic Exception block as SQLAlchemyError should cover DB issues,
    # and context manager handles rollback.

async def get_items_by_list_id(db: AsyncSession, list_id: int) -> PyList[ItemModel]:
    """Gets all items belonging to a specific list, ordered by creation time."""
    try:
        stmt = (
            select(ItemModel)
            .where(ItemModel.list_id == list_id)
            .options(
                selectinload(ItemModel.added_by_user),
                selectinload(ItemModel.completed_by_user)
            )
            .order_by(ItemModel.created_at.asc())
        )
        result = await db.execute(stmt)
        return result.scalars().all()
    except OperationalError as e:
        raise DatabaseConnectionError(f"Failed to connect to database: {str(e)}")
    except SQLAlchemyError as e:
        raise DatabaseQueryError(f"Failed to query items: {str(e)}")

async def get_item_by_id(db: AsyncSession, item_id: int) -> Optional[ItemModel]:
    """Gets a single item by its ID."""
    try:
        stmt = (
            select(ItemModel)
            .where(ItemModel.id == item_id)
            .options(
                selectinload(ItemModel.added_by_user),
                selectinload(ItemModel.completed_by_user),
                selectinload(ItemModel.list) # Often useful to get the parent list
            )
        )
        result = await db.execute(stmt)
        return result.scalars().first()
    except OperationalError as e:
        raise DatabaseConnectionError(f"Failed to connect to database: {str(e)}")
    except SQLAlchemyError as e:
        raise DatabaseQueryError(f"Failed to query item: {str(e)}")

async def update_item(db: AsyncSession, item_db: ItemModel, item_in: ItemUpdate, user_id: int) -> ItemModel:
    """Updates an existing item record, checking for version conflicts and handling reordering."""
    try:
        async with db.begin_nested() if db.in_transaction() else db.begin() as transaction:
            if item_db.version != item_in.version:
                raise ConflictError(
                    f"Item '{item_db.name}' (ID: {item_db.id}) has been modified. "
                    f"Your version is {item_in.version}, current version is {item_db.version}. Please refresh."
                )

            update_data = item_in.model_dump(exclude_unset=True, exclude={'version'})
            
            # --- Handle Reordering ---
            if 'position' in update_data:
                new_position = update_data.pop('position') # Remove from update_data to handle separately
                
                # We need the full list to reorder, making sure it's loaded and ordered
                list_id = item_db.list_id
                stmt = select(ItemModel).where(ItemModel.list_id == list_id).order_by(ItemModel.position.asc(), ItemModel.created_at.asc())
                result = await db.execute(stmt)
                items_in_list = result.scalars().all()

                # Find the item to move
                item_to_move = next((it for it in items_in_list if it.id == item_db.id), None)
                if item_to_move:
                    items_in_list.remove(item_to_move)
                    # Insert at the new position (adjust for 1-based index from frontend)
                    # Clamp position to be within bounds
                    insert_pos = max(0, min(new_position - 1, len(items_in_list)))
                    items_in_list.insert(insert_pos, item_to_move)

                    # Re-assign positions
                    for i, item in enumerate(items_in_list):
                        item.position = i + 1

            # --- End Handle Reordering ---

            if 'is_complete' in update_data:
                if update_data['is_complete'] is True:
                    if item_db.completed_by_id is None:
                        update_data['completed_by_id'] = user_id
                else:
                    update_data['completed_by_id'] = None
            
            for key, value in update_data.items():
                setattr(item_db, key, value)

            item_db.version += 1
            db.add(item_db) # Mark as dirty
            await db.flush()

            # Re-fetch with relationships
            stmt = (
                select(ItemModel)
                .where(ItemModel.id == item_db.id)
                .options(
                    selectinload(ItemModel.added_by_user),
                    selectinload(ItemModel.completed_by_user),
                    selectinload(ItemModel.list)
                )
            )
            result = await db.execute(stmt)
            updated_item = result.scalar_one_or_none()

            if updated_item is None: # Should not happen
                # Rollback will be handled by context manager on raise
                raise ItemOperationError("Failed to load item after update.")

            return updated_item
    except IntegrityError as e:
        logger.error(f"Database integrity error during item update: {str(e)}", exc_info=True)
        raise DatabaseIntegrityError(f"Failed to update item due to integrity constraint: {str(e)}")
    except OperationalError as e:
        logger.error(f"Database connection error while updating item: {str(e)}", exc_info=True)
        raise DatabaseConnectionError(f"Database connection error while updating item: {str(e)}")
    except ConflictError: # Re-raise ConflictError, rollback handled by context manager
        raise
    except SQLAlchemyError as e:
        logger.error(f"Unexpected SQLAlchemy error during item update: {str(e)}", exc_info=True)
        raise DatabaseTransactionError(f"Failed to update item: {str(e)}")

async def delete_item(db: AsyncSession, item_db: ItemModel) -> None:
    """Deletes an item record. Version check should be done by the caller (API endpoint)."""
    try:
        async with db.begin_nested() if db.in_transaction() else db.begin() as transaction:
            await db.delete(item_db)
            # await transaction.commit() # Removed
        # No return needed for None
    except OperationalError as e:
        logger.error(f"Database connection error while deleting item: {str(e)}", exc_info=True)
        raise DatabaseConnectionError(f"Database connection error while deleting item: {str(e)}")
    except SQLAlchemyError as e:
        logger.error(f"Unexpected SQLAlchemy error while deleting item: {str(e)}", exc_info=True)
        raise DatabaseTransactionError(f"Failed to delete item: {str(e)}")

# Ensure ItemOperationError is defined in app.core.exceptions if used
# Example: class ItemOperationError(AppException): pass