//! Spot-related database queries.

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

use crate::models::{CreateSpotRequest, Spot, SpotSummary, UpdateSpotRequest};
use chattyness_error::AppError;

/// List all spots for a scene.
pub async fn list_spots_for_scene<'e>(
    executor: impl PgExecutor<'e>,
    scene_id: Uuid,
) -> Result<Vec<SpotSummary>, AppError> {
    let spots = sqlx::query_as::<_, SpotSummary>(
        r#"
        SELECT
            id,
            name,
            slug,
            spot_type,
            ST_AsText(region) as region_wkt,
            sort_order,
            is_visible,
            is_active
        FROM realm.spots
        WHERE scene_id = $1
        ORDER BY sort_order ASC, name ASC NULLS LAST
        "#,
    )
    .bind(scene_id)
    .fetch_all(executor)
    .await?;

    Ok(spots)
}

/// Get a spot by its ID.
pub async fn get_spot_by_id<'e>(
    executor: impl PgExecutor<'e>,
    spot_id: Uuid,
) -> Result<Option<Spot>, AppError> {
    let spot = sqlx::query_as::<_, Spot>(
        r#"
        SELECT
            id,
            scene_id,
            name,
            slug,
            ST_AsText(region) as region_wkt,
            spot_type,
            destination_scene_id,
            ST_AsText(destination_position) as destination_position_wkt,
            current_state,
            sort_order,
            is_visible,
            is_active,
            created_at,
            updated_at
        FROM realm.spots
        WHERE id = $1
        "#,
    )
    .bind(spot_id)
    .fetch_optional(executor)
    .await?;

    Ok(spot)
}

/// Get a spot by scene ID and slug.
pub async fn get_spot_by_slug<'e>(
    executor: impl PgExecutor<'e>,
    scene_id: Uuid,
    slug: &str,
) -> Result<Option<Spot>, AppError> {
    let spot = sqlx::query_as::<_, Spot>(
        r#"
        SELECT
            id,
            scene_id,
            name,
            slug,
            ST_AsText(region) as region_wkt,
            spot_type,
            destination_scene_id,
            ST_AsText(destination_position) as destination_position_wkt,
            current_state,
            sort_order,
            is_visible,
            is_active,
            created_at,
            updated_at
        FROM realm.spots
        WHERE scene_id = $1 AND slug = $2
        "#,
    )
    .bind(scene_id)
    .bind(slug)
    .fetch_optional(executor)
    .await?;

    Ok(spot)
}

/// Check if a spot slug is available within a scene.
pub async fn is_spot_slug_available<'e>(
    executor: impl PgExecutor<'e>,
    scene_id: Uuid,
    slug: &str,
) -> Result<bool, AppError> {
    let exists: (bool,) =
        sqlx::query_as(r#"SELECT EXISTS(SELECT 1 FROM realm.spots WHERE scene_id = $1 AND slug = $2)"#)
            .bind(scene_id)
            .bind(slug)
            .fetch_one(executor)
            .await?;

    Ok(!exists.0)
}

/// Create a new spot.
pub async fn create_spot<'e>(
    executor: impl PgExecutor<'e>,
    scene_id: Uuid,
    req: &CreateSpotRequest,
) -> Result<Spot, AppError> {
    let spot_type = req.spot_type.unwrap_or_default().to_string();
    let sort_order = req.sort_order.unwrap_or(0);
    let is_visible = req.is_visible.unwrap_or(true);
    let is_active = req.is_active.unwrap_or(true);

    let spot = sqlx::query_as::<_, Spot>(
        r#"
        INSERT INTO realm.spots (
            scene_id, name, slug,
            region, spot_type,
            destination_scene_id, destination_position,
            sort_order, is_visible, is_active
        )
        VALUES (
            $1, $2, $3,
            ST_GeomFromText($4, 0), $5::realm.spot_type,
            $6, CASE WHEN $7 IS NOT NULL THEN ST_GeomFromText($7, 0) ELSE NULL END,
            $8, $9, $10
        )
        RETURNING
            id,
            scene_id,
            name,
            slug,
            ST_AsText(region) as region_wkt,
            spot_type,
            destination_scene_id,
            ST_AsText(destination_position) as destination_position_wkt,
            current_state,
            sort_order,
            is_visible,
            is_active,
            created_at,
            updated_at
        "#,
    )
    .bind(scene_id)
    .bind(&req.name)
    .bind(&req.slug)
    .bind(&req.region_wkt)
    .bind(&spot_type)
    .bind(req.destination_scene_id)
    .bind(&req.destination_position_wkt)
    .bind(sort_order)
    .bind(is_visible)
    .bind(is_active)
    .fetch_one(executor)
    .await?;

    Ok(spot)
}

/// Update a spot.
pub async fn update_spot<'e>(
    executor: impl PgExecutor<'e>,
    spot_id: Uuid,
    req: &UpdateSpotRequest,
) -> Result<Spot, AppError> {
    // Build dynamic update query
    let mut set_clauses = Vec::new();
    let mut param_idx = 2; // $1 is spot_id

    if req.name.is_some() {
        set_clauses.push(format!("name = ${}", param_idx));
        param_idx += 1;
    }
    if req.slug.is_some() {
        set_clauses.push(format!("slug = ${}", param_idx));
        param_idx += 1;
    }
    if req.region_wkt.is_some() {
        set_clauses.push(format!("region = ST_GeomFromText(${}, 0)", param_idx));
        param_idx += 1;
    }
    if req.spot_type.is_some() {
        set_clauses.push(format!("spot_type = ${}::realm.spot_type", param_idx));
        param_idx += 1;
    }
    if req.destination_scene_id.is_some() {
        set_clauses.push(format!("destination_scene_id = ${}", param_idx));
        param_idx += 1;
    }
    if req.destination_position_wkt.is_some() {
        set_clauses.push(format!(
            "destination_position = CASE WHEN ${} IS NOT NULL THEN ST_GeomFromText(${}, 0) ELSE NULL END",
            param_idx, param_idx
        ));
        param_idx += 1;
    }
    if req.current_state.is_some() {
        set_clauses.push(format!("current_state = ${}", param_idx));
        param_idx += 1;
    }
    if req.sort_order.is_some() {
        set_clauses.push(format!("sort_order = ${}", param_idx));
        param_idx += 1;
    }
    if req.is_visible.is_some() {
        set_clauses.push(format!("is_visible = ${}", param_idx));
        param_idx += 1;
    }
    if req.is_active.is_some() {
        set_clauses.push(format!("is_active = ${}", param_idx));
    }

    // If no updates, just return the current spot via SELECT
    let query = if set_clauses.is_empty() {
        r#"SELECT id, scene_id, name, slug, ST_AsText(region) as region_wkt,
                  spot_type, destination_scene_id,
                  ST_AsText(destination_position) as destination_position_wkt,
                  current_state, sort_order, is_visible, is_active,
                  created_at, updated_at
           FROM realm.spots WHERE id = $1"#.to_string()
    } else {
        set_clauses.push("updated_at = now()".to_string());
        format!(
            r#"UPDATE realm.spots SET {}
               WHERE id = $1
               RETURNING id, scene_id, name, slug, ST_AsText(region) as region_wkt,
                         spot_type, destination_scene_id,
                         ST_AsText(destination_position) as destination_position_wkt,
                         current_state, sort_order, is_visible, is_active,
                         created_at, updated_at"#,
            set_clauses.join(", ")
        )
    };

    let mut query_builder = sqlx::query_as::<_, Spot>(&query).bind(spot_id);

    if let Some(ref name) = req.name {
        query_builder = query_builder.bind(name);
    }
    if let Some(ref slug) = req.slug {
        query_builder = query_builder.bind(slug);
    }
    if let Some(ref region_wkt) = req.region_wkt {
        query_builder = query_builder.bind(region_wkt);
    }
    if let Some(ref spot_type) = req.spot_type {
        query_builder = query_builder.bind(spot_type.to_string());
    }
    if let Some(destination_scene_id) = req.destination_scene_id {
        query_builder = query_builder.bind(destination_scene_id);
    }
    if let Some(ref destination_position_wkt) = req.destination_position_wkt {
        query_builder = query_builder.bind(destination_position_wkt);
    }
    if let Some(current_state) = req.current_state {
        query_builder = query_builder.bind(current_state);
    }
    if let Some(sort_order) = req.sort_order {
        query_builder = query_builder.bind(sort_order);
    }
    if let Some(is_visible) = req.is_visible {
        query_builder = query_builder.bind(is_visible);
    }
    if let Some(is_active) = req.is_active {
        query_builder = query_builder.bind(is_active);
    }

    let spot = query_builder
        .fetch_optional(executor)
        .await?
        .ok_or_else(|| AppError::NotFound("Spot not found".to_string()))?;

    Ok(spot)
}

/// Delete a spot.
pub async fn delete_spot<'e>(
    executor: impl PgExecutor<'e>,
    spot_id: Uuid,
) -> Result<(), AppError> {
    let result = sqlx::query(r#"DELETE FROM realm.spots WHERE id = $1"#)
        .bind(spot_id)
        .execute(executor)
        .await?;

    if result.rows_affected() == 0 {
        return Err(AppError::NotFound("Spot not found".to_string()));
    }

    Ok(())
}

/// Get the next sort order for a new spot in a scene.
pub async fn get_next_sort_order<'e>(
    executor: impl PgExecutor<'e>,
    scene_id: Uuid,
) -> Result<i32, AppError> {
    let result: (Option<i32>,) =
        sqlx::query_as(r#"SELECT MAX(sort_order) FROM realm.spots WHERE scene_id = $1"#)
            .bind(scene_id)
            .fetch_one(executor)
            .await?;

    Ok(result.0.unwrap_or(0) + 1)
}
