use axum::extract::Extension;
use axum::http::StatusCode;
use axum::Json;
use mongodb::bson::doc;
use serde::Deserialize;

use compliance_core::models::notification::CveNotification;
use compliance_core::tenant_ctx::TenantCtx;

use super::dto::{tenant_db, AgentExt, ApiResponse};

/// GET /api/v1/notifications — List CVE notifications (newest first)
#[tracing::instrument(skip_all)]
pub async fn list_notifications(
    Extension(agent): AgentExt,
    tenant: TenantCtx,
    axum::extract::Query(params): axum::extract::Query<NotificationFilter>,
) -> Result<Json<ApiResponse<Vec<CveNotification>>>, StatusCode> {
    let db = tenant_db(&agent, &tenant).await?;
    let mut filter = doc! {};

    // Filter by status (default: show new + read, exclude dismissed)
    match params.status.as_deref() {
        Some("all") => {}
        Some(s) => {
            filter.insert("status", s);
        }
        None => {
            filter.insert("status", doc! { "$in": ["new", "read"] });
        }
    }

    // Filter by severity
    if let Some(ref sev) = params.severity {
        filter.insert("severity", sev.as_str());
    }

    // Filter by repo
    if let Some(ref repo_id) = params.repo_id {
        filter.insert("repo_id", repo_id.as_str());
    }

    let page = params.page.unwrap_or(1).max(1);
    let limit = params.limit.unwrap_or(50).min(200);
    let skip = (page - 1) * limit as u64;

    let total = db
        .cve_notifications()
        .count_documents(filter.clone())
        .await
        .unwrap_or(0);

    let notifications: Vec<CveNotification> = match db
        .cve_notifications()
        .find(filter)
        .sort(doc! { "created_at": -1 })
        .skip(skip)
        .limit(limit)
        .await
    {
        Ok(cursor) => {
            use futures_util::StreamExt;
            let mut items = Vec::new();
            let mut cursor = cursor;
            while let Some(Ok(n)) = cursor.next().await {
                items.push(n);
            }
            items
        }
        Err(e) => {
            tracing::error!("Failed to list notifications: {e}");
            return Err(StatusCode::INTERNAL_SERVER_ERROR);
        }
    };

    Ok(Json(ApiResponse {
        data: notifications,
        total: Some(total),
        page: Some(page),
    }))
}

/// GET /api/v1/notifications/count — Count of unread notifications
#[tracing::instrument(skip_all)]
pub async fn notification_count(
    Extension(agent): AgentExt,
    tenant: TenantCtx,
) -> Result<Json<serde_json::Value>, StatusCode> {
    let db = tenant_db(&agent, &tenant).await?;
    let count = db
        .cve_notifications()
        .count_documents(doc! { "status": "new" })
        .await
        .unwrap_or(0);

    Ok(Json(serde_json::json!({ "count": count })))
}

/// PATCH /api/v1/notifications/:id/read — Mark a notification as read
#[tracing::instrument(skip_all, fields(id = %id))]
pub async fn mark_read(
    Extension(agent): AgentExt,
    tenant: TenantCtx,
    axum::extract::Path(id): axum::extract::Path<String>,
) -> Result<Json<serde_json::Value>, StatusCode> {
    let oid = mongodb::bson::oid::ObjectId::parse_str(&id).map_err(|_| StatusCode::BAD_REQUEST)?;
    let db = tenant_db(&agent, &tenant).await?;

    let result = db
        .cve_notifications()
        .update_one(
            doc! { "_id": oid },
            doc! { "$set": {
                "status": "read",
                "read_at": mongodb::bson::DateTime::now(),
            }},
        )
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    if result.matched_count == 0 {
        return Err(StatusCode::NOT_FOUND);
    }
    Ok(Json(serde_json::json!({ "status": "read" })))
}

/// PATCH /api/v1/notifications/:id/dismiss — Dismiss a notification
#[tracing::instrument(skip_all, fields(id = %id))]
pub async fn dismiss_notification(
    Extension(agent): AgentExt,
    tenant: TenantCtx,
    axum::extract::Path(id): axum::extract::Path<String>,
) -> Result<Json<serde_json::Value>, StatusCode> {
    let oid = mongodb::bson::oid::ObjectId::parse_str(&id).map_err(|_| StatusCode::BAD_REQUEST)?;
    let db = tenant_db(&agent, &tenant).await?;

    let result = db
        .cve_notifications()
        .update_one(
            doc! { "_id": oid },
            doc! { "$set": { "status": "dismissed" } },
        )
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    if result.matched_count == 0 {
        return Err(StatusCode::NOT_FOUND);
    }
    Ok(Json(serde_json::json!({ "status": "dismissed" })))
}

/// POST /api/v1/notifications/read-all — Mark all new notifications as read
#[tracing::instrument(skip_all)]
pub async fn mark_all_read(
    Extension(agent): AgentExt,
    tenant: TenantCtx,
) -> Result<Json<serde_json::Value>, StatusCode> {
    let db = tenant_db(&agent, &tenant).await?;
    let result = db
        .cve_notifications()
        .update_many(
            doc! { "status": "new" },
            doc! { "$set": {
                "status": "read",
                "read_at": mongodb::bson::DateTime::now(),
            }},
        )
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok(Json(
        serde_json::json!({ "updated": result.modified_count }),
    ))
}

#[derive(Debug, Deserialize)]
pub struct NotificationFilter {
    pub status: Option<String>,
    pub severity: Option<String>,
    pub repo_id: Option<String>,
    pub page: Option<u64>,
    pub limit: Option<i64>,
}
