use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    Json,
};
use std::sync::Arc;
use uuid::Uuid;

use crate::{
    middleware::auth::AuthUser,
    models::{
        self, CreateRoomRequest, MessagePayload, PaginationParams, Room, RoomResponse, UserPublic,
    },
    AppState,
};

/// Create a room, persist it, and add the creator as the first member.
pub async fn create_room(
    State(state): State<Arc<AppState>>,
    auth: AuthUser,
    Json(body): Json<CreateRoomRequest>,
) -> Result<Json<RoomResponse>, (StatusCode, String)> {
    let room_id = Uuid::new_v4().to_string();

    sqlx::query(
        "INSERT INTO rooms (id, name, model_id, created_by, ai_always_respond, system_prompt, ai_name) VALUES (?, ?, ?, ?, ?, ?, ?)",
    )
    .bind(&room_id)
    .bind(&body.name)
    .bind(&body.model_id)
    .bind(&auth.user_id)
    .bind(body.ai_always_respond)
    .bind(&body.system_prompt)
    .bind(&body.ai_name)
    .execute(&state.db)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    // Auto-join creator
    sqlx::query("INSERT INTO room_members (room_id, user_id) VALUES (?, ?)")
        .bind(&room_id)
        .bind(&auth.user_id)
        .execute(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(RoomResponse {
        id: room_id,
        name: body.name,
        model_id: body.model_id,
        created_by: auth.user_id.clone(),
        ai_always_respond: body.ai_always_respond,
        system_prompt: body.system_prompt,
        ai_name: body.ai_name,
        created_at: chrono::Utc::now().to_rfc3339(),
        members: vec![UserPublic {
            id: auth.user_id,
            email: models::public_email(&auth.email),
            display_name: auth.display_name,
            avatar_url: None,
            nostr_pubkey: None,
        }],
    }))
}

/// List all active rooms the caller belongs to, including current room members.
pub async fn list_rooms(
    State(state): State<Arc<AppState>>,
    auth: AuthUser,
) -> Result<Json<Vec<RoomResponse>>, (StatusCode, String)> {
    let rooms = sqlx::query_as::<_, Room>(
        "SELECT r.* FROM rooms r JOIN room_members rm ON r.id = rm.room_id WHERE rm.user_id = ? AND r.deleted_at IS NULL ORDER BY r.created_at DESC",
    )
    .bind(&auth.user_id)
    .fetch_all(&state.db)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let mut result = Vec::new();
    for room in rooms {
        let members = sqlx::query_as::<_, (String, String, String, Option<String>, Option<String>)>(
            "SELECT u.id, u.email, u.display_name, u.avatar_url, u.nostr_pubkey FROM users u JOIN room_members rm ON u.id = rm.user_id WHERE rm.room_id = ?",
        )
        .bind(&room.id)
        .fetch_all(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

        result.push(RoomResponse {
            id: room.id,
            name: room.name,
            model_id: room.model_id,
            created_by: room.created_by,
            ai_always_respond: room.ai_always_respond,
            system_prompt: room.system_prompt,
            ai_name: room.ai_name,
            created_at: room.created_at,
            members: members
                .into_iter()
                .map(
                    |(id, email, display_name, avatar_url, nostr_pubkey)| UserPublic {
                        id,
                        email: models::public_email(&email),
                        display_name,
                        avatar_url,
                        nostr_pubkey,
                    },
                )
                .collect(),
        });
    }

    Ok(Json(result))
}

/// Return details for a single room after verifying the caller is a member.
pub async fn get_room(
    State(state): State<Arc<AppState>>,
    auth: AuthUser,
    Path(room_id): Path<String>,
) -> Result<Json<RoomResponse>, (StatusCode, String)> {
    // Verify membership
    let is_member = sqlx::query_scalar::<_, String>(
        "SELECT user_id FROM room_members WHERE room_id = ? AND user_id = ?",
    )
    .bind(&room_id)
    .bind(&auth.user_id)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    if is_member.is_none() {
        return Err((StatusCode::FORBIDDEN, "Not a member of this room".into()));
    }

    let room = sqlx::query_as::<_, Room>("SELECT * FROM rooms WHERE id = ? AND deleted_at IS NULL")
        .bind(&room_id)
        .fetch_optional(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
        .ok_or((StatusCode::NOT_FOUND, "Room not found".into()))?;

    let members = sqlx::query_as::<_, (String, String, String, Option<String>, Option<String>)>(
        "SELECT u.id, u.email, u.display_name, u.avatar_url, u.nostr_pubkey FROM users u JOIN room_members rm ON u.id = rm.user_id WHERE rm.room_id = ?",
    )
    .bind(&room_id)
    .fetch_all(&state.db)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(RoomResponse {
        id: room.id,
        name: room.name,
        model_id: room.model_id,
        created_by: room.created_by,
        ai_always_respond: room.ai_always_respond,
        system_prompt: room.system_prompt,
        ai_name: room.ai_name,
        created_at: room.created_at,
        members: members
            .into_iter()
            .map(
                |(id, email, display_name, avatar_url, nostr_pubkey)| UserPublic {
                    id,
                    email: models::public_email(&email),
                    display_name,
                    avatar_url,
                    nostr_pubkey,
                },
            )
            .collect(),
    }))
}

/// Return paginated message history for a room the caller can access.
pub async fn get_messages(
    State(state): State<Arc<AppState>>,
    auth: AuthUser,
    Path(room_id): Path<String>,
    Query(params): Query<PaginationParams>,
) -> Result<Json<Vec<MessagePayload>>, (StatusCode, String)> {
    // Verify membership and room not deleted
    let is_member = sqlx::query_scalar::<_, String>(
        "SELECT rm.user_id FROM room_members rm JOIN rooms r ON r.id = rm.room_id WHERE rm.room_id = ? AND rm.user_id = ? AND r.deleted_at IS NULL",
    )
    .bind(&room_id)
    .bind(&auth.user_id)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    if is_member.is_none() {
        return Err((StatusCode::FORBIDDEN, "Not a member of this room".into()));
    }

    // Query messages with user email + avatar_url via LEFT JOIN
    let rows = if let Some(before) = &params.before {
        sqlx::query_as::<_, (String, String, String, String, String, String, bool, String, Option<String>, Option<String>, Option<String>, Option<String>, Option<String>)>(
            "SELECT m.id, m.room_id, m.sender_id, m.sender_name, m.content, m.mentions, m.is_ai, m.created_at, m.ai_meta, m.image_url, u.email, u.avatar_url, m.hash \
             FROM messages m LEFT JOIN users u ON m.sender_id = u.id \
             WHERE m.room_id = ? AND m.created_at < ? ORDER BY m.created_at DESC LIMIT ?",
        )
        .bind(&room_id)
        .bind(before)
        .bind(params.limit)
        .fetch_all(&state.db)
        .await
    } else {
        sqlx::query_as::<_, (String, String, String, String, String, String, bool, String, Option<String>, Option<String>, Option<String>, Option<String>, Option<String>)>(
            "SELECT m.id, m.room_id, m.sender_id, m.sender_name, m.content, m.mentions, m.is_ai, m.created_at, m.ai_meta, m.image_url, u.email, u.avatar_url, m.hash \
             FROM messages m LEFT JOIN users u ON m.sender_id = u.id \
             WHERE m.room_id = ? ORDER BY m.created_at DESC LIMIT ?",
        )
        .bind(&room_id)
        .bind(params.limit)
        .fetch_all(&state.db)
        .await
    }
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    // The SQL query reads newest-first for efficient pagination, but clients
    // render chat oldest-to-newest, so reverse the rows before serializing.
    let payloads: Vec<MessagePayload> = rows
        .into_iter()
        .rev()
        .map(
            |(
                id,
                room_id,
                sender_id,
                sender_name,
                content,
                mentions,
                is_ai,
                created_at,
                ai_meta_str,
                image_url,
                email,
                avatar_url,
                hash,
            )| {
                let ai_meta = ai_meta_str
                    .as_deref()
                    .and_then(|s| serde_json::from_str::<crate::models::AiMeta>(s).ok());
                let avatar_hash = email
                    .map(|e| crate::models::gravatar_hash(&e))
                    .unwrap_or_default();
                MessagePayload {
                    id,
                    room_id,
                    sender_id,
                    sender_name,
                    content,
                    mentions: serde_json::from_str(&mentions).unwrap_or_default(),
                    is_ai,
                    created_at,
                    ai_meta,
                    avatar_hash,
                    avatar_url,
                    image_url,
                    hash,
                }
            },
        )
        .collect();

    Ok(Json(payloads))
}

/// Resolve a stable message hash into the room that contains it.
pub async fn resolve_message_hash(
    State(state): State<Arc<AppState>>,
    auth: AuthUser,
    Path(hash): Path<String>,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
    // Find the message by hash
    let row = sqlx::query_as::<_, (String,)>(
        "SELECT m.room_id FROM messages m \
         JOIN room_members rm ON rm.room_id = m.room_id AND rm.user_id = ? \
         JOIN rooms r ON r.id = m.room_id AND r.deleted_at IS NULL \
         WHERE m.hash = ? LIMIT 1",
    )
    .bind(&auth.user_id)
    .bind(&hash)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    match row {
        Some((room_id,)) => Ok(Json(
            serde_json::json!({ "room_id": room_id, "hash": hash }),
        )),
        None => Err((
            StatusCode::NOT_FOUND,
            "Message not found or no access".into(),
        )),
    }
}

/// Add the caller to a room directly when they already know its ID.
pub async fn join_room(
    State(state): State<Arc<AppState>>,
    auth: AuthUser,
    Path(room_id): Path<String>,
) -> Result<StatusCode, (StatusCode, String)> {
    // Check room exists
    let room_exists =
        sqlx::query_scalar::<_, String>("SELECT id FROM rooms WHERE id = ? AND deleted_at IS NULL")
            .bind(&room_id)
            .fetch_optional(&state.db)
            .await
            .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    if room_exists.is_none() {
        return Err((StatusCode::NOT_FOUND, "Room not found".into()));
    }

    sqlx::query("INSERT OR IGNORE INTO room_members (room_id, user_id) VALUES (?, ?)")
        .bind(&room_id)
        .bind(&auth.user_id)
        .execute(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(StatusCode::OK)
}

/// Soft-delete a room and broadcast the deletion event to connected members.
pub async fn delete_room(
    State(state): State<Arc<AppState>>,
    auth: AuthUser,
    Path(room_id): Path<String>,
) -> Result<StatusCode, (StatusCode, String)> {
    // Fetch room and verify ownership
    let room = sqlx::query_as::<_, Room>("SELECT * FROM rooms WHERE id = ? AND deleted_at IS NULL")
        .bind(&room_id)
        .fetch_optional(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
        .ok_or((StatusCode::NOT_FOUND, "Room not found".into()))?;

    if room.created_by != auth.user_id {
        return Err((
            StatusCode::FORBIDDEN,
            "Only the room creator can delete this room".into(),
        ));
    }

    // Soft-delete
    sqlx::query("UPDATE rooms SET deleted_at = datetime('now') WHERE id = ?")
        .bind(&room_id)
        .execute(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    tracing::info!("Room {} soft-deleted by {}", room_id, auth.user_id);

    // Broadcast to all subscribers
    let _ = state.tx.send(crate::models::BroadcastEvent {
        room_id: room_id.clone(),
        message: crate::models::WsServerMessage::RoomDeleted { room_id },
    });

    Ok(StatusCode::OK)
}

/// Permanently remove all messages from a room without deleting the room itself.
pub async fn clear_room(
    State(state): State<Arc<AppState>>,
    auth: AuthUser,
    Path(room_id): Path<String>,
) -> Result<StatusCode, (StatusCode, String)> {
    // Verify room exists and not deleted
    let room = sqlx::query_as::<_, Room>("SELECT * FROM rooms WHERE id = ? AND deleted_at IS NULL")
        .bind(&room_id)
        .fetch_optional(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
        .ok_or((StatusCode::NOT_FOUND, "Room not found".into()))?;

    if room.created_by != auth.user_id {
        return Err((
            StatusCode::FORBIDDEN,
            "Only the room creator can clear messages".into(),
        ));
    }

    // Hard-delete all messages
    sqlx::query("DELETE FROM messages WHERE room_id = ?")
        .bind(&room_id)
        .execute(&state.db)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    tracing::info!("Room {} messages cleared by {}", room_id, auth.user_id);

    // Broadcast to all subscribers
    let _ = state.tx.send(crate::models::BroadcastEvent {
        room_id: room_id.clone(),
        message: crate::models::WsServerMessage::RoomCleared { room_id },
    });

    Ok(StatusCode::OK)
}
