//! Mixer-kanaler — HTTP API for delt lydmixer-tilstand.
//!
//! Skriver direkte til PG; NOTIFY-trigger propagerer endringer til
//! WebSocket-klienter.

use axum::{extract::State, http::StatusCode, Json};
use serde::{Deserialize, Serialize};

use crate::auth::AuthUser;
use crate::AppState;

#[derive(Serialize)]
pub struct ErrorResponse {
    pub error: String,
}

fn bad_request(msg: &str) -> (StatusCode, Json<ErrorResponse>) {
    (StatusCode::BAD_REQUEST, Json(ErrorResponse { error: msg.to_string() }))
}

fn internal_error(msg: &str) -> (StatusCode, Json<ErrorResponse>) {
    (StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: msg.to_string() }))
}

// =============================================================================
// Create mixer channel
// =============================================================================

#[derive(Deserialize)]
pub struct CreateMixerChannelRequest {
    pub room_id: String,
    pub target_user_id: String,
}

#[derive(Serialize)]
pub struct MixerChannelResponse {
    pub ok: bool,
}

pub async fn create_mixer_channel(
    State(state): State<AppState>,
    _user: AuthUser,
    Json(req): Json<CreateMixerChannelRequest>,
) -> Result<Json<MixerChannelResponse>, (StatusCode, Json<ErrorResponse>)> {
    if req.room_id.is_empty() || req.target_user_id.is_empty() {
        return Err(bad_request("room_id og target_user_id er påkrevd"));
    }

    sqlx::query(
        r#"
        INSERT INTO mixer_channels (room_id, target_user_id, updated_by, updated_at)
        VALUES ($1, $2, $3, now())
        ON CONFLICT (room_id, target_user_id) DO NOTHING
        "#,
    )
    .bind(&req.room_id)
    .bind(&req.target_user_id)
    .bind(&req.target_user_id) // updated_by = the user joining
    .execute(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("Feil ved opprettelse av mixer-kanal: {e}");
        internal_error("Databasefeil")
    })?;

    Ok(Json(MixerChannelResponse { ok: true }))
}

// =============================================================================
// Set gain
// =============================================================================

#[derive(Deserialize)]
pub struct SetGainRequest {
    pub room_id: String,
    pub target_user_id: String,
    pub gain: f64,
}

pub async fn set_gain(
    State(state): State<AppState>,
    user: AuthUser,
    Json(req): Json<SetGainRequest>,
) -> Result<Json<MixerChannelResponse>, (StatusCode, Json<ErrorResponse>)> {
    sqlx::query(
        "UPDATE mixer_channels SET gain = $1, updated_by = $4, updated_at = now() WHERE room_id = $2 AND target_user_id = $3"
    )
    .bind(req.gain.clamp(0.0, 1.5))
    .bind(&req.room_id)
    .bind(&req.target_user_id)
    .bind(user.node_id.to_string())
    .execute(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("Feil ved set_gain: {e}");
        internal_error("Databasefeil")
    })?;

    Ok(Json(MixerChannelResponse { ok: true }))
}

// =============================================================================
// Set mute
// =============================================================================

#[derive(Deserialize)]
pub struct SetMuteRequest {
    pub room_id: String,
    pub target_user_id: String,
    pub is_muted: bool,
}

pub async fn set_mute(
    State(state): State<AppState>,
    user: AuthUser,
    Json(req): Json<SetMuteRequest>,
) -> Result<Json<MixerChannelResponse>, (StatusCode, Json<ErrorResponse>)> {
    sqlx::query(
        "UPDATE mixer_channels SET is_muted = $1, updated_by = $4, updated_at = now() WHERE room_id = $2 AND target_user_id = $3"
    )
    .bind(req.is_muted)
    .bind(&req.room_id)
    .bind(&req.target_user_id)
    .bind(user.node_id.to_string())
    .execute(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("Feil ved set_mute: {e}");
        internal_error("Databasefeil")
    })?;

    Ok(Json(MixerChannelResponse { ok: true }))
}

// =============================================================================
// Toggle effect
// =============================================================================

#[derive(Deserialize)]
pub struct ToggleEffectRequest {
    pub room_id: String,
    pub target_user_id: String,
    pub effect_name: String,
}

pub async fn toggle_effect(
    State(state): State<AppState>,
    user: AuthUser,
    Json(req): Json<ToggleEffectRequest>,
) -> Result<Json<MixerChannelResponse>, (StatusCode, Json<ErrorResponse>)> {
    // Atomisk toggle via PG JSON-operasjoner — unngår race condition ved
    // samtidige oppdateringer fra flere klienter.
    sqlx::query(
        r#"UPDATE mixer_channels
           SET active_effects = (
               CASE
                   WHEN (active_effects::jsonb->>$3)::boolean IS NOT DISTINCT FROM true
                   THEN (active_effects::jsonb || jsonb_build_object($3, false))::text
                   ELSE (active_effects::jsonb || jsonb_build_object($3, true))::text
               END
           ),
           updated_by = $4,
           updated_at = now()
           WHERE room_id = $1 AND target_user_id = $2"#,
    )
    .bind(&req.room_id)
    .bind(&req.target_user_id)
    .bind(&req.effect_name)
    .bind(user.node_id.to_string())
    .execute(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("Feil ved toggle_effect: {e}");
        internal_error("Databasefeil")
    })?;

    Ok(Json(MixerChannelResponse { ok: true }))
}

// =============================================================================
// Set role
// =============================================================================

#[derive(Deserialize)]
pub struct SetMixerRoleRequest {
    pub room_id: String,
    pub target_user_id: String,
    pub role: String,
}

pub async fn set_mixer_role(
    State(state): State<AppState>,
    user: AuthUser,
    Json(req): Json<SetMixerRoleRequest>,
) -> Result<Json<MixerChannelResponse>, (StatusCode, Json<ErrorResponse>)> {
    let valid_roles = ["editor", "viewer"];
    if !valid_roles.contains(&req.role.as_str()) {
        return Err(bad_request("Ugyldig rolle. Gyldige: editor, viewer"));
    }

    sqlx::query(
        "UPDATE mixer_channels SET role = $1, updated_by = $4, updated_at = now() WHERE room_id = $2 AND target_user_id = $3"
    )
    .bind(&req.role)
    .bind(&req.room_id)
    .bind(&req.target_user_id)
    .bind(user.node_id.to_string())
    .execute(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("Feil ved set_mixer_role: {e}");
        internal_error("Databasefeil")
    })?;

    Ok(Json(MixerChannelResponse { ok: true }))
}
