//! Loose props database queries.
//!
//! Handles props dropped in channels that can be picked up by users.

use sqlx::PgExecutor;
use uuid::Uuid;

use crate::models::{InventoryItem, LooseProp};
use chattyness_error::AppError;

/// List all loose props in a channel (excluding expired).
pub async fn list_channel_loose_props<'e>(
    executor: impl PgExecutor<'e>,
    channel_id: Uuid,
) -> Result<Vec<LooseProp>, AppError> {
    let props = sqlx::query_as::<_, LooseProp>(
        r#"
        SELECT
            lp.id,
            lp.channel_id,
            lp.server_prop_id,
            lp.realm_prop_id,
            ST_X(lp.position) as position_x,
            ST_Y(lp.position) as position_y,
            lp.dropped_by,
            lp.expires_at,
            lp.created_at,
            COALESCE(sp.name, rp.name) as prop_name,
            COALESCE(sp.asset_path, rp.asset_path) as prop_asset_path
        FROM props.loose_props lp
        LEFT JOIN server.props sp ON lp.server_prop_id = sp.id
        LEFT JOIN props.realm_props rp ON lp.realm_prop_id = rp.id
        WHERE lp.channel_id = $1
          AND (lp.expires_at IS NULL OR lp.expires_at > now())
        ORDER BY lp.created_at ASC
        "#,
    )
    .bind(channel_id)
    .fetch_all(executor)
    .await?;

    Ok(props)
}

/// Drop a prop from inventory to the canvas.
///
/// Deletes from inventory and inserts into loose_props with 30-minute expiry.
/// Returns the created loose prop.
pub async fn drop_prop_to_canvas<'e>(
    executor: impl PgExecutor<'e>,
    inventory_item_id: Uuid,
    user_id: Uuid,
    channel_id: Uuid,
    position_x: f64,
    position_y: f64,
) -> Result<LooseProp, AppError> {
    // Use a CTE to delete from inventory and insert to loose_props in one query
    let prop = sqlx::query_as::<_, LooseProp>(
        r#"
        WITH deleted_item AS (
            DELETE FROM props.inventory
            WHERE id = $1 AND user_id = $2
            RETURNING id, server_prop_id, realm_prop_id, prop_name, prop_asset_path
        ),
        inserted_prop AS (
            INSERT INTO props.loose_props (
                channel_id,
                server_prop_id,
                realm_prop_id,
                position,
                dropped_by,
                expires_at
            )
            SELECT
                $3,
                di.server_prop_id,
                di.realm_prop_id,
                public.make_virtual_point($4::real, $5::real),
                $2,
                now() + interval '30 minutes'
            FROM deleted_item di
            RETURNING
                id,
                channel_id,
                server_prop_id,
                realm_prop_id,
                ST_X(position) as position_x,
                ST_Y(position) as position_y,
                dropped_by,
                expires_at,
                created_at
        )
        SELECT
            ip.id,
            ip.channel_id,
            ip.server_prop_id,
            ip.realm_prop_id,
            ip.position_x,
            ip.position_y,
            ip.dropped_by,
            ip.expires_at,
            ip.created_at,
            di.prop_name,
            di.prop_asset_path
        FROM inserted_prop ip
        CROSS JOIN deleted_item di
        "#,
    )
    .bind(inventory_item_id)
    .bind(user_id)
    .bind(channel_id)
    .bind(position_x as f32)
    .bind(position_y as f32)
    .fetch_optional(executor)
    .await?
    .ok_or_else(|| {
        AppError::NotFound("Inventory item not found or not owned by user".to_string())
    })?;

    Ok(prop)
}

/// Pick up a loose prop (delete from loose_props, insert to inventory).
///
/// Returns the created inventory item.
pub async fn pick_up_loose_prop<'e>(
    executor: impl PgExecutor<'e>,
    loose_prop_id: Uuid,
    user_id: Uuid,
) -> Result<InventoryItem, AppError> {
    // Use a CTE to delete from loose_props and insert to inventory
    let item = sqlx::query_as::<_, InventoryItem>(
        r#"
        WITH deleted_prop AS (
            DELETE FROM props.loose_props
            WHERE id = $1
              AND (expires_at IS NULL OR expires_at > now())
            RETURNING id, server_prop_id, realm_prop_id
        ),
        source_info AS (
            SELECT
                COALESCE(sp.name, rp.name) as prop_name,
                COALESCE(sp.asset_path, rp.asset_path) as prop_asset_path,
                COALESCE(sp.default_layer, rp.default_layer) as layer,
                COALESCE(sp.is_transferable, rp.is_transferable) as is_transferable,
                COALESCE(sp.is_portable, true) as is_portable,
                dp.server_prop_id,
                dp.realm_prop_id
            FROM deleted_prop dp
            LEFT JOIN server.props sp ON dp.server_prop_id = sp.id
            LEFT JOIN props.realm_props rp ON dp.realm_prop_id = rp.id
        ),
        inserted_item AS (
            INSERT INTO props.inventory (
                user_id,
                server_prop_id,
                realm_prop_id,
                prop_name,
                prop_asset_path,
                layer,
                origin,
                is_transferable,
                is_portable,
                provenance,
                acquired_at
            )
            SELECT
                $2,
                si.server_prop_id,
                si.realm_prop_id,
                si.prop_name,
                si.prop_asset_path,
                si.layer,
                'server_library'::props.prop_origin,
                COALESCE(si.is_transferable, true),
                COALESCE(si.is_portable, true),
                '[]'::jsonb,
                now()
            FROM source_info si
            RETURNING id, prop_name, prop_asset_path, layer, is_transferable, is_portable, acquired_at
        )
        SELECT
            ii.id,
            ii.prop_name,
            ii.prop_asset_path,
            ii.layer,
            ii.is_transferable,
            ii.is_portable,
            'server_library'::props.prop_origin as origin,
            ii.acquired_at
        FROM inserted_item ii
        "#,
    )
    .bind(loose_prop_id)
    .bind(user_id)
    .fetch_optional(executor)
    .await?
    .ok_or_else(|| AppError::NotFound("Loose prop not found or has expired".to_string()))?;

    Ok(item)
}

/// Delete expired loose props.
///
/// Returns the number of props deleted.
pub async fn cleanup_expired_props<'e>(executor: impl PgExecutor<'e>) -> Result<u64, AppError> {
    let result = sqlx::query(
        r#"
        DELETE FROM props.loose_props
        WHERE expires_at IS NOT NULL AND expires_at <= now()
        "#,
    )
    .execute(executor)
    .await?;

    Ok(result.rows_affected())
}
