pub mod model;

use crate::{
  AppState,
  auth::{Session, SessionAuthorization, middleware::check_cookie_from_header_str},
  error::{Error, Result},
};
use axum::{
  Json,
  Router,
  extract::{
    Path,
    State,
    WebSocketUpgrade,
    ws::{Message, WebSocket},
  },
  http::{HeaderMap, StatusCode},
  response::IntoResponse,
  routing::{delete, get, post, put},
};
use futures_util::{SinkExt, StreamExt};
use model::{
  AccessRequestWithUser,
  ClientMessage,
  CreateAccessRequestPayload,
  CreateMapPayload,
  GridCell,
  GridMap,
  GridToken,
  ListedMap,
  MapRole,
  MapState,
  PermissionWithUser,
  ResolveAccessRequestPayload,
  ServerMessage,
  UpdateMapPayload,
  UpdatePermissionPayload,
};
use siren_core::utils::csprng;
use std::sync::Arc;
use tokio::sync::broadcast;
use uuid::Uuid;

pub fn get_routes() -> Router<Arc<AppState>> {
  Router::new()
    .route("/maps", get(list_maps))
    .route("/maps", post(create_map))
    .route("/maps/{id}", get(get_map))
    .route("/maps/{id}", put(update_map))
    .route("/maps/{id}", delete(delete_map))
    .route("/maps/{id}/permissions", get(list_permissions))
    .route("/maps/{id}/permissions", put(update_permission))
    .route("/maps/{id}/favorite", post(favorite_map))
    .route("/maps/{id}/favorite", delete(unfavorite_map))
    .route("/maps/{id}/access-requests", post(create_access_request))
    .route("/maps/{id}/access-requests", get(list_access_requests))
    .route(
      "/maps/{id}/access-requests/{request_id}",
      put(resolve_access_request),
    )
    .route("/maps/{id}/ws", get(ws_handler))
}

/// Fetch the role of `user_id` on `map_id`, or `None` if no record exists.
async fn get_user_role(map_id: &str, user_id: Uuid) -> Result<Option<MapRole>> {
  let pool = siren_core::data::pool();
  let role: Option<String> =
    sqlx::query_scalar("SELECT role FROM map_permissions WHERE map_id = $1 AND user_id = $2")
      .bind(map_id)
      .bind(user_id)
      .fetch_optional(pool)
      .await?;
  Ok(role.and_then(|r| match r.as_str() {
    "owner" => Some(MapRole::Owner),
    "editor" => Some(MapRole::Editor),
    "viewer" => Some(MapRole::Viewer),
    _ => None,
  }))
}

/// Returns whether the caller can view the map.
async fn can_view(map: &GridMap, session: &Option<Session>) -> bool {
  if map.public_access == "public_view" || map.public_access == "public_edit" {
    return true;
  }
  let Some(s) = session else { return false };
  get_user_role(&map.id, s.user_id)
    .await
    .ok()
    .flatten()
    .is_some()
}

/// Returns whether the caller can edit the map (editor or owner role, or public_edit).
async fn can_edit(map: &GridMap, session: &Option<Session>) -> bool {
  if map.public_access == "public_edit" {
    return true;
  }
  let Some(s) = session else { return false };
  get_user_role(&map.id, s.user_id)
    .await
    .ok()
    .flatten()
    .map(|r| r.can_edit())
    .unwrap_or(false)
}

/// Returns whether the caller is the owner.
async fn is_owner(map: &GridMap, session: &Option<Session>) -> bool {
  let Some(s) = session else { return false };
  get_user_role(&map.id, s.user_id)
    .await
    .ok()
    .flatten()
    .map(|r| r.is_owner())
    .unwrap_or(false)
}

pub async fn list_maps(
  SessionAuthorization(session): SessionAuthorization,
) -> Result<Json<Vec<ListedMap>>> {
  let pool = siren_core::data::pool();
  let maps: Vec<ListedMap> = match &session {
    Some(s) => {
      sqlx::query_as(
        "SELECT
           gm.id, gm.name, gm.public_access, gm.owner_id,
           u.username AS owner_username,
           gm.colors, gm.created_at, gm.updated_at,
           mp.role AS user_role,
           (mf.user_id IS NOT NULL) AS is_favorited
         FROM grid_maps gm
         JOIN users u ON u.id = gm.owner_id
         LEFT JOIN map_permissions mp ON mp.map_id = gm.id AND mp.user_id = $1
         LEFT JOIN map_favorites mf ON mf.map_id = gm.id AND mf.user_id = $1
         WHERE mp.user_id IS NOT NULL OR mf.user_id IS NOT NULL
         ORDER BY gm.updated_at DESC",
      )
      .bind(s.user_id)
      .fetch_all(pool)
      .await?
    }
    None => vec![],
  };
  Ok(Json(maps))
}

pub async fn create_map(
  SessionAuthorization(session): SessionAuthorization,
  Json(payload): Json<CreateMapPayload>,
) -> Result<(StatusCode, Json<GridMap>)> {
  let session = session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;

  let public_access = payload.public_access.as_str();
  if !matches!(public_access, "private" | "public_view" | "public_edit") {
    return Err(Error::new(422, "Invalid public_access value".into()));
  }

  let map_id = csprng(32);
  let pool = siren_core::data::pool();

  let map: GridMap = sqlx::query_as(
    "INSERT INTO grid_maps (id, name, public_access, owner_id)
     VALUES ($1, $2, $3, $4)
     RETURNING *",
  )
  .bind(&map_id)
  .bind(&payload.name)
  .bind(&payload.public_access)
  .bind(session.user_id)
  .fetch_one(pool)
  .await?;

  // Auto-assign the creator as owner in map_permissions
  sqlx::query("INSERT INTO map_permissions (map_id, user_id, role) VALUES ($1, $2, 'owner')")
    .bind(&map_id)
    .bind(session.user_id)
    .execute(pool)
    .await?;

  Ok((StatusCode::CREATED, Json(map)))
}

pub async fn get_map(
  SessionAuthorization(session): SessionAuthorization,
  Path(id): Path<String>,
) -> Result<Json<MapState>> {
  let pool = siren_core::data::pool();

  let map: Option<GridMap> = sqlx::query_as("SELECT * FROM grid_maps WHERE id = $1")
    .bind(&id)
    .fetch_optional(pool)
    .await?;

  let map = map.ok_or_else(|| Error::not_found("Map not found".into()))?;

  if !can_view(&map, &session).await {
    return Err(StatusCode::FORBIDDEN.into());
  }

  let cells: Vec<GridCell> = sqlx::query_as("SELECT * FROM grid_cells WHERE map_id = $1")
    .bind(&id)
    .fetch_all(pool)
    .await?;

  let tokens: Vec<GridToken> = sqlx::query_as("SELECT * FROM grid_tokens WHERE map_id = $1")
    .bind(&id)
    .fetch_all(pool)
    .await?;

  Ok(Json(MapState { map, cells, tokens }))
}

pub async fn update_map(
  SessionAuthorization(session): SessionAuthorization,
  Path(id): Path<String>,
  Json(payload): Json<UpdateMapPayload>,
) -> Result<Json<GridMap>> {
  let pool = siren_core::data::pool();

  let map: Option<GridMap> = sqlx::query_as("SELECT * FROM grid_maps WHERE id = $1")
    .bind(&id)
    .fetch_optional(pool)
    .await?;

  let map = map.ok_or_else(|| Error::not_found("Map not found".into()))?;

  if !is_owner(&map, &session).await {
    return Err(StatusCode::FORBIDDEN.into());
  }

  if let Some(ref pa) = payload.public_access {
    if !matches!(pa.as_str(), "private" | "public_view" | "public_edit") {
      return Err(Error::new(422, "Invalid public_access value".into()));
    }
  }

  let new_name = payload.name.as_deref().unwrap_or(&map.name);
  let new_pa = payload
    .public_access
    .as_deref()
    .unwrap_or(&map.public_access);

  let updated: GridMap = sqlx::query_as(
    "UPDATE grid_maps SET name = $1, public_access = $2, updated_at = NOW()
     WHERE id = $3 RETURNING *",
  )
  .bind(new_name)
  .bind(new_pa)
  .bind(&id)
  .fetch_one(pool)
  .await?;

  Ok(Json(updated))
}

pub async fn delete_map(
  SessionAuthorization(session): SessionAuthorization,
  Path(id): Path<String>,
) -> Result<StatusCode> {
  let pool = siren_core::data::pool();

  let map: Option<GridMap> = sqlx::query_as("SELECT * FROM grid_maps WHERE id = $1")
    .bind(&id)
    .fetch_optional(pool)
    .await?;

  let map = map.ok_or_else(|| Error::not_found("Map not found".into()))?;

  if !is_owner(&map, &session).await {
    return Err(StatusCode::FORBIDDEN.into());
  }

  sqlx::query("DELETE FROM grid_maps WHERE id = $1")
    .bind(&id)
    .execute(pool)
    .await?;

  Ok(StatusCode::NO_CONTENT)
}

pub async fn list_permissions(
  SessionAuthorization(session): SessionAuthorization,
  Path(id): Path<String>,
) -> Result<Json<Vec<PermissionWithUser>>> {
  let pool = siren_core::data::pool();

  let map: Option<GridMap> = sqlx::query_as("SELECT * FROM grid_maps WHERE id = $1")
    .bind(&id)
    .fetch_optional(pool)
    .await?;

  let map = map.ok_or_else(|| Error::not_found("Map not found".into()))?;

  if !is_owner(&map, &session).await {
    return Err(StatusCode::FORBIDDEN.into());
  }

  let perms: Vec<PermissionWithUser> = sqlx::query_as(
    "SELECT mp.map_id, mp.user_id, u.username, mp.role
     FROM map_permissions mp
     JOIN users u ON u.id = mp.user_id
     WHERE mp.map_id = $1
     ORDER BY mp.role, u.username",
  )
  .bind(&id)
  .fetch_all(pool)
  .await?;

  Ok(Json(perms))
}

pub async fn update_permission(
  SessionAuthorization(session): SessionAuthorization,
  Path(id): Path<String>,
  Json(payload): Json<UpdatePermissionPayload>,
) -> Result<StatusCode> {
  let pool = siren_core::data::pool();

  let map: Option<GridMap> = sqlx::query_as("SELECT * FROM grid_maps WHERE id = $1")
    .bind(&id)
    .fetch_optional(pool)
    .await?;

  let map = map.ok_or_else(|| Error::not_found("Map not found".into()))?;

  if !is_owner(&map, &session).await {
    return Err(StatusCode::FORBIDDEN.into());
  }

  // Resolve username → user_id
  let target_id: Option<Uuid> = sqlx::query_scalar("SELECT id FROM users WHERE username = $1")
    .bind(&payload.username)
    .fetch_optional(pool)
    .await?;

  let target_id = target_id.ok_or_else(|| Error::not_found("User not found".into()))?;

  // Prevent the owner from stripping their own owner record
  if let Some(ref s) = session {
    if target_id == s.user_id {
      if let Some(ref role) = payload.role {
        if !role.is_owner() {
          return Err(Error::from(StatusCode::UNPROCESSABLE_ENTITY));
        }
      }
    }
  }

  match payload.role {
    Some(role) => {
      sqlx::query(
        "INSERT INTO map_permissions (map_id, user_id, role)
         VALUES ($1, $2, $3)
         ON CONFLICT (map_id, user_id) DO UPDATE SET role = EXCLUDED.role",
      )
      .bind(&id)
      .bind(target_id)
      .bind(role)
      .execute(pool)
      .await?;
    }
    None => {
      sqlx::query("DELETE FROM map_permissions WHERE map_id = $1 AND user_id = $2")
        .bind(&id)
        .bind(target_id)
        .execute(pool)
        .await?;
    }
  }

  Ok(StatusCode::NO_CONTENT)
}

pub async fn favorite_map(
  SessionAuthorization(session): SessionAuthorization,
  Path(id): Path<String>,
) -> Result<StatusCode> {
  let session = session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;
  let pool = siren_core::data::pool();

  // Verify the map exists and is viewable
  let map: Option<GridMap> = sqlx::query_as("SELECT * FROM grid_maps WHERE id = $1")
    .bind(&id)
    .fetch_optional(pool)
    .await?;
  let map = map.ok_or_else(|| Error::not_found("Map not found".into()))?;
  if !can_view(&map, &Some(session.clone())).await {
    return Err(StatusCode::FORBIDDEN.into());
  }

  sqlx::query(
    "INSERT INTO map_favorites (user_id, map_id) VALUES ($1, $2)
     ON CONFLICT DO NOTHING",
  )
  .bind(session.user_id)
  .bind(&id)
  .execute(pool)
  .await?;

  Ok(StatusCode::NO_CONTENT)
}

pub async fn unfavorite_map(
  SessionAuthorization(session): SessionAuthorization,
  Path(id): Path<String>,
) -> Result<StatusCode> {
  let session = session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;
  let pool = siren_core::data::pool();

  sqlx::query("DELETE FROM map_favorites WHERE user_id = $1 AND map_id = $2")
    .bind(session.user_id)
    .bind(&id)
    .execute(pool)
    .await?;

  Ok(StatusCode::NO_CONTENT)
}

pub async fn create_access_request(
  SessionAuthorization(session): SessionAuthorization,
  Path(id): Path<String>,
  Json(payload): Json<CreateAccessRequestPayload>,
) -> Result<StatusCode> {
  let session = session.ok_or_else(|| Error::from(StatusCode::UNAUTHORIZED))?;

  // Only editor and viewer roles can be requested
  if matches!(payload.role, MapRole::Owner) {
    return Err(Error::new(422, "Cannot request owner role".into()));
  }

  let pool = siren_core::data::pool();

  let map: Option<GridMap> = sqlx::query_as("SELECT * FROM grid_maps WHERE id = $1")
    .bind(&id)
    .fetch_optional(pool)
    .await?;
  map.ok_or_else(|| Error::not_found("Map not found".into()))?;

  // Check if user already has a direct permission
  let existing_role = get_user_role(&id, session.user_id).await?;
  if existing_role.is_some() {
    return Err(Error::new(
      409,
      "You already have access to this map".into(),
    ));
  }

  // Upsert the request (update role if they change their mind)
  sqlx::query(
    "INSERT INTO map_access_requests (map_id, user_id, requested_role, status, updated_at)
     VALUES ($1, $2, $3, 'pending', NOW())
     ON CONFLICT (map_id, user_id)
     DO UPDATE SET requested_role = EXCLUDED.requested_role,
                   status = 'pending',
                   updated_at = NOW()",
  )
  .bind(&id)
  .bind(session.user_id)
  .bind(&payload.role)
  .execute(pool)
  .await?;

  Ok(StatusCode::CREATED)
}

pub async fn list_access_requests(
  SessionAuthorization(session): SessionAuthorization,
  Path(id): Path<String>,
) -> Result<Json<Vec<AccessRequestWithUser>>> {
  let pool = siren_core::data::pool();

  let map: Option<GridMap> = sqlx::query_as("SELECT * FROM grid_maps WHERE id = $1")
    .bind(&id)
    .fetch_optional(pool)
    .await?;
  let map = map.ok_or_else(|| Error::not_found("Map not found".into()))?;

  if !is_owner(&map, &session).await {
    return Err(StatusCode::FORBIDDEN.into());
  }

  let requests: Vec<AccessRequestWithUser> = sqlx::query_as(
    "SELECT mar.id, mar.map_id, mar.user_id, u.username,
            mar.requested_role, mar.status, mar.created_at, mar.updated_at
     FROM map_access_requests mar
     JOIN users u ON u.id = mar.user_id
     WHERE mar.map_id = $1 AND mar.status = 'pending'
     ORDER BY mar.created_at ASC",
  )
  .bind(&id)
  .fetch_all(pool)
  .await?;

  Ok(Json(requests))
}

pub async fn resolve_access_request(
  SessionAuthorization(session): SessionAuthorization,
  Path((map_id, request_id)): Path<(String, Uuid)>,
  Json(payload): Json<ResolveAccessRequestPayload>,
) -> Result<StatusCode> {
  let pool = siren_core::data::pool();

  let map: Option<GridMap> = sqlx::query_as("SELECT * FROM grid_maps WHERE id = $1")
    .bind(&map_id)
    .fetch_optional(pool)
    .await?;
  let map = map.ok_or_else(|| Error::not_found("Map not found".into()))?;

  if !is_owner(&map, &session).await {
    return Err(StatusCode::FORBIDDEN.into());
  }

  if !matches!(payload.action.as_str(), "approve" | "deny") {
    return Err(Error::new(422, "action must be 'approve' or 'deny'".into()));
  }

  // Fetch the request
  let req: Option<(Uuid, String)> = sqlx::query_as(
    "SELECT user_id, requested_role FROM map_access_requests WHERE id = $1 AND map_id = $2",
  )
  .bind(request_id)
  .bind(&map_id)
  .fetch_optional(pool)
  .await?;

  let (user_id, role) = req.ok_or_else(|| Error::not_found("Access request not found".into()))?;

  if payload.action == "approve" {
    // Grant the requested role
    sqlx::query(
      "INSERT INTO map_permissions (map_id, user_id, role)
       VALUES ($1, $2, $3)
       ON CONFLICT (map_id, user_id) DO UPDATE SET role = EXCLUDED.role",
    )
    .bind(&map_id)
    .bind(user_id)
    .bind(&role)
    .execute(pool)
    .await?;
  }

  // Mark request as resolved
  let new_status = if payload.action == "approve" {
    "approved"
  } else {
    "denied"
  };
  sqlx::query("UPDATE map_access_requests SET status = $1, updated_at = NOW() WHERE id = $2")
    .bind(new_status)
    .bind(request_id)
    .execute(pool)
    .await?;

  Ok(StatusCode::NO_CONTENT)
}

pub async fn ws_handler(
  ws: WebSocketUpgrade,
  State(state): State<Arc<AppState>>,
  Path(map_id): Path<String>,
  headers: HeaderMap,
) -> impl IntoResponse {
  let session: Option<Session> = {
    let ip = crate::auth::middleware::extract_ip(&headers);
    let user_agent = headers
      .get("user-agent")
      .and_then(|h| h.to_str().ok())
      .unwrap_or("unknown");
    if let Some(cookie_header) = headers.get("cookie").and_then(|h| h.to_str().ok()) {
      check_cookie_from_header_str(cookie_header, &ip, user_agent).await
    } else {
      None
    }
  };

  ws.on_upgrade(move |socket| handle_socket(socket, state, map_id, session))
}

async fn handle_socket(
  socket: WebSocket,
  state: Arc<AppState>,
  map_id: String,
  session: Option<Session>,
) {
  let map_state = match fetch_map_state(&map_id).await {
    Ok(ms) => ms,
    Err(_) => return,
  };

  if !can_view(&map_state.map, &session).await {
    return;
  }

  let editor = can_edit(&map_state.map, &session).await;

  let tx = state
    .map_rooms
    .entry(map_id.clone())
    .or_insert_with(|| {
      let (tx, _) = broadcast::channel(256);
      tx
    })
    .clone();
  let mut rx = tx.subscribe();

  let (mut ws_tx, mut ws_rx) = socket.split();

  let init_msg = ServerMessage::State {
    cells: map_state.cells,
    tokens: map_state.tokens,
    colors: map_state.map.colors,
  };
  if let Ok(json) = serde_json::to_string(&init_msg) {
    let _ = ws_tx.send(Message::Text(json.into())).await;
  }

  let mut send_task = tokio::spawn(async move {
    while let Ok(json) = rx.recv().await {
      if ws_tx.send(Message::Text(json.into())).await.is_err() {
        break;
      }
    }
  });

  let tx_clone = tx.clone();
  let mut recv_task = tokio::spawn(async move {
    while let Some(Ok(msg)) = ws_rx.next().await {
      match msg {
        Message::Text(text) => {
          handle_client_message(&text, &map_id, editor, &tx_clone).await;
        }
        Message::Close(_) => break,
        _ => {}
      }
    }
  });

  tokio::select! {
      _ = &mut send_task => recv_task.abort(),
      _ = &mut recv_task => send_task.abort(),
  }
}

async fn fetch_map_state(map_id: &str) -> crate::error::Result<MapState> {
  let pool = siren_core::data::pool();

  let map: GridMap = sqlx::query_as("SELECT * FROM grid_maps WHERE id = $1")
    .bind(map_id)
    .fetch_one(pool)
    .await?;

  let cells: Vec<GridCell> = sqlx::query_as("SELECT * FROM grid_cells WHERE map_id = $1")
    .bind(map_id)
    .fetch_all(pool)
    .await?;

  let tokens: Vec<GridToken> = sqlx::query_as("SELECT * FROM grid_tokens WHERE map_id = $1")
    .bind(map_id)
    .fetch_all(pool)
    .await?;

  Ok(MapState { map, cells, tokens })
}

async fn handle_client_message(
  raw: &str,
  map_id: &str,
  can_edit: bool,
  tx: &broadcast::Sender<String>,
) {
  let client_msg: ClientMessage = match serde_json::from_str(raw) {
    Ok(m) => m,
    Err(e) => {
      log::warn!("Invalid WS message: {e}");
      return;
    }
  };

  if !can_edit {
    let err = ServerMessage::Error {
      message: "You do not have permission to edit this map.".into(),
    };
    if let Ok(json) = serde_json::to_string(&err) {
      let _ = tx.send(json);
    }
    return;
  }

  let pool = siren_core::data::pool();

  let server_msg: Option<ServerMessage> = match client_msg {
    ClientMessage::PaintCell { x, y, color } => {
      let result = sqlx::query(
        "INSERT INTO grid_cells (map_id, x, y, color)
         VALUES ($1, $2, $3, $4)
         ON CONFLICT (map_id, x, y) DO UPDATE SET color = EXCLUDED.color",
      )
      .bind(map_id)
      .bind(x)
      .bind(y)
      .bind(&color)
      .execute(pool)
      .await;

      match result {
        Ok(_) => Some(ServerMessage::CellPainted { x, y, color }),
        Err(e) => {
          log::error!("DB error painting cell: {e}");
          None
        }
      }
    }

    ClientMessage::PaintCells { cells } => {
      let mut tx_db = match pool.begin().await {
        Ok(t) => t,
        Err(e) => {
          log::error!("DB error starting transaction for batch paint: {e}");
          return;
        }
      };

      let mut ok = true;
      for cell in &cells {
        let res = sqlx::query(
          "INSERT INTO grid_cells (map_id, x, y, color)
           VALUES ($1, $2, $3, $4)
           ON CONFLICT (map_id, x, y) DO UPDATE SET color = EXCLUDED.color",
        )
        .bind(map_id)
        .bind(cell.x)
        .bind(cell.y)
        .bind(&cell.color)
        .execute(&mut *tx_db)
        .await;

        if let Err(e) = res {
          log::error!("DB error in batch paint cell ({},{}): {e}", cell.x, cell.y);
          ok = false;
          break;
        }
      }

      if ok {
        if let Err(e) = tx_db.commit().await {
          log::error!("DB error committing batch paint: {e}");
          None
        } else {
          Some(ServerMessage::CellsBatchPainted { cells })
        }
      } else {
        let _ = tx_db.rollback().await;
        None
      }
    }

    ClientMessage::EraseCell { x, y } => {
      let result = sqlx::query("DELETE FROM grid_cells WHERE map_id = $1 AND x = $2 AND y = $3")
        .bind(map_id)
        .bind(x)
        .bind(y)
        .execute(pool)
        .await;

      match result {
        Ok(_) => Some(ServerMessage::CellErased { x, y }),
        Err(e) => {
          log::error!("DB error erasing cell: {e}");
          None
        }
      }
    }

    ClientMessage::AddToken { x, y, label, color } => {
      let token_id = csprng(16);
      let result: sqlx::Result<GridToken> = sqlx::query_as(
        "INSERT INTO grid_tokens (id, map_id, x, y, label, color)
         VALUES ($1, $2, $3, $4, $5, $6) RETURNING *",
      )
      .bind(&token_id)
      .bind(map_id)
      .bind(x)
      .bind(y)
      .bind(&label)
      .bind(&color)
      .fetch_one(pool)
      .await;

      match result {
        Ok(token) => Some(ServerMessage::TokenAdded {
          id: token.id,
          x: token.x,
          y: token.y,
          label: token.label,
          color: token.color,
        }),
        Err(e) => {
          log::error!("DB error adding token: {e}");
          None
        }
      }
    }

    ClientMessage::MoveToken { id, x, y } => {
      let result =
        sqlx::query("UPDATE grid_tokens SET x = $1, y = $2 WHERE id = $3 AND map_id = $4")
          .bind(x)
          .bind(y)
          .bind(&id)
          .bind(map_id)
          .execute(pool)
          .await;

      match result {
        Ok(r) if r.rows_affected() > 0 => Some(ServerMessage::TokenMoved { id, x, y }),
        Ok(_) => None,
        Err(e) => {
          log::error!("DB error moving token: {e}");
          None
        }
      }
    }

    ClientMessage::DeleteToken { id } => {
      let result = sqlx::query("DELETE FROM grid_tokens WHERE id = $1 AND map_id = $2")
        .bind(&id)
        .bind(map_id)
        .execute(pool)
        .await;

      match result {
        Ok(r) if r.rows_affected() > 0 => Some(ServerMessage::TokenDeleted { id }),
        Ok(_) => None,
        Err(e) => {
          log::error!("DB error deleting token: {e}");
          None
        }
      }
    }

    ClientMessage::UpdateColors { colors } => {
      let result =
        sqlx::query("UPDATE grid_maps SET colors = $1, updated_at = NOW() WHERE id = $2")
          .bind(&colors)
          .bind(map_id)
          .execute(pool)
          .await;

      match result {
        Ok(_) => Some(ServerMessage::ColorsUpdated { colors }),
        Err(e) => {
          log::error!("DB error updating colors: {e}");
          None
        }
      }
    }
  };

  if let Some(msg) = server_msg {
    if let Ok(json) = serde_json::to_string(&msg) {
      let _ = tx.send(json);
    }
  }
}
