use crate::AppState;
use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
    routing::{get, patch},
    Json, Router,
};
use contracts::auth_middleware::AuthUser;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub fn router() -> Router<AppState> {
    Router::new()
        .route("/", get(list_notifications))
        .route("/unread-count", get(unread_count))
        .route("/{id}/read", patch(mark_read))
        .route("/read-all", patch(mark_all_read))
}

// ── Query params ──────────────────────────────────────────────────────────────

#[derive(Deserialize)]
struct ListQuery {
    page: Option<i64>,
    limit: Option<i64>,
}

// ── Response types ────────────────────────────────────────────────────────────

#[derive(Serialize)]
pub struct NotificationDto {
    pub id: Uuid,
    pub title: String,
    pub body: Option<String>,
    #[serde(rename = "type")]
    pub notification_type: Option<String>,
    pub reference_id: Option<Uuid>,
    pub is_read: bool,
    pub created_at: String,
}

#[derive(Serialize)]
pub struct PaginatedResponse<T> {
    pub data: Vec<T>,
    pub pagination: Pagination,
}

#[derive(Serialize)]
pub struct Pagination {
    pub page: i64,
    pub limit: i64,
    pub total: i64,
    pub total_pages: i64,
}

// ── FromRow structs ──────────────────────────────────────────────────────────

#[derive(sqlx::FromRow)]
struct NotificationRow {
    id: Uuid,
    title: String,
    body: Option<String>,
    notification_type: Option<String>,
    reference_id: Option<Uuid>,
    is_read: bool,
    created_at: chrono::DateTime<chrono::Utc>,
}

// ── Handlers ──────────────────────────────────────────────────────────────────

async fn list_notifications(
    auth: AuthUser,
    State(state): State<AppState>,
    Query(params): Query<ListQuery>,
) -> impl IntoResponse {
    let page = params.page.unwrap_or(1).max(1);
    let limit = params.limit.unwrap_or(20).clamp(1, 100);
    let offset = (page - 1) * limit;

    let rows = sqlx::query_as::<_, NotificationRow>(
        r#"
        SELECT id, title, body, type AS notification_type,
               reference_id, is_read, created_at
        FROM notifications
        WHERE user_id = $1
        ORDER BY created_at DESC
        LIMIT $2 OFFSET $3
        "#,
    )
    .bind(auth.user_id)
    .bind(limit)
    .bind(offset)
    .fetch_all(&state.pool)
    .await;

    let total: i64 = sqlx::query_scalar::<_, i64>(
        "SELECT COUNT(*) FROM notifications WHERE user_id = $1",
    )
    .bind(auth.user_id)
    .fetch_one(&state.pool)
    .await
    .unwrap_or(0);

    match rows {
        Ok(rows) => {
            let data = rows
                .into_iter()
                .map(|r| NotificationDto {
                    id: r.id,
                    title: r.title,
                    body: r.body,
                    notification_type: r.notification_type,
                    reference_id: r.reference_id,
                    is_read: r.is_read,
                    created_at: r.created_at.to_rfc3339(),
                })
                .collect();

            let total_pages = if total == 0 { 1 } else { (total + limit - 1) / limit };

            (
                StatusCode::OK,
                Json(PaginatedResponse {
                    data,
                    pagination: Pagination { page, limit, total, total_pages },
                }),
            )
                .into_response()
        }
        Err(e) => {
            tracing::error!("Failed to fetch notifications: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": "Failed to fetch notifications" })),
            )
                .into_response()
        }
    }
}

async fn unread_count(
    auth: AuthUser,
    State(state): State<AppState>,
) -> impl IntoResponse {
    let count: i64 = sqlx::query_scalar::<_, i64>(
        "SELECT COUNT(*) FROM notifications WHERE user_id = $1 AND is_read = false",
    )
    .bind(auth.user_id)
    .fetch_one(&state.pool)
    .await
    .unwrap_or(0);

    (StatusCode::OK, Json(serde_json::json!({ "unread_count": count })))
}

async fn mark_read(
    auth: AuthUser,
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    let result = sqlx::query(
        "UPDATE notifications SET is_read = true WHERE id = $1 AND user_id = $2",
    )
    .bind(id)
    .bind(auth.user_id)
    .execute(&state.pool)
    .await;

    match result {
        Ok(r) if r.rows_affected() == 0 => (
            StatusCode::NOT_FOUND,
            Json(serde_json::json!({ "error": "Notification not found" })),
        )
            .into_response(),
        Ok(_) => (
            StatusCode::OK,
            Json(serde_json::json!({ "id": id, "is_read": true })),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to mark notification as read: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": "Failed to update notification" })),
            )
                .into_response()
        }
    }
}

async fn mark_all_read(
    auth: AuthUser,
    State(state): State<AppState>,
) -> impl IntoResponse {
    let result = sqlx::query(
        "UPDATE notifications SET is_read = true WHERE user_id = $1 AND is_read = false",
    )
    .bind(auth.user_id)
    .execute(&state.pool)
    .await;

    match result {
        Ok(r) => (
            StatusCode::OK,
            Json(serde_json::json!({ "updated": r.rows_affected() })),
        )
            .into_response(),
        Err(e) => {
            tracing::error!("Failed to mark all notifications as read: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": "Failed to update notifications" })),
            )
                .into_response()
        }
    }
}
