use axum::extract::{Extension, Path, Query};
use axum::http::StatusCode;
use axum::Json;
use mongodb::bson::doc;

use super::dto::*;
use compliance_core::models::Finding;
use compliance_core::tenant_ctx::TenantCtx;

#[tracing::instrument(skip_all, fields(repo_id = ?filter.repo_id, severity = ?filter.severity, scan_type = ?filter.scan_type))]
pub async fn list_findings(
    Extension(agent): AgentExt,
    tenant: TenantCtx,
    Query(filter): Query<FindingsFilter>,
) -> ApiResult<Vec<Finding>> {
    let db = tenant_db(&agent, &tenant).await?;
    let db = &db;
    let mut query = doc! {};
    if let Some(repo_id) = &filter.repo_id {
        query.insert("repo_id", repo_id);
    }
    if let Some(severity) = &filter.severity {
        query.insert("severity", severity);
    }
    if let Some(scan_type) = &filter.scan_type {
        query.insert("scan_type", scan_type);
    }
    if let Some(status) = &filter.status {
        query.insert("status", status);
    }
    // Text search across title, description, file_path, rule_id
    if let Some(q) = &filter.q {
        if !q.is_empty() {
            let regex = doc! { "$regex": q, "$options": "i" };
            query.insert(
                "$or",
                mongodb::bson::bson!([
                    { "title": regex.clone() },
                    { "description": regex.clone() },
                    { "file_path": regex.clone() },
                    { "rule_id": regex },
                ]),
            );
        }
    }

    // Dynamic sort
    let sort_field = filter.sort_by.as_deref().unwrap_or("created_at");
    let sort_dir: i32 = match filter.sort_order.as_deref() {
        Some("asc") => 1,
        _ => -1,
    };
    let sort_doc = doc! { sort_field: sort_dir };

    let skip = (filter.page.saturating_sub(1)) * filter.limit as u64;
    let total = db
        .findings()
        .count_documents(query.clone())
        .await
        .unwrap_or(0);

    let findings = match db
        .findings()
        .find(query)
        .sort(sort_doc)
        .skip(skip)
        .limit(filter.limit)
        .await
    {
        Ok(cursor) => collect_cursor_async(cursor).await,
        Err(e) => {
            tracing::warn!("Failed to fetch findings: {e}");
            Vec::new()
        }
    };

    Ok(Json(ApiResponse {
        data: findings,
        total: Some(total),
        page: Some(filter.page),
    }))
}

#[tracing::instrument(skip_all, fields(finding_id = %id))]
pub async fn get_finding(
    Extension(agent): AgentExt,
    tenant: TenantCtx,
    Path(id): Path<String>,
) -> Result<Json<ApiResponse<Finding>>, StatusCode> {
    let oid = mongodb::bson::oid::ObjectId::parse_str(&id).map_err(|_| StatusCode::BAD_REQUEST)?;
    let db = tenant_db(&agent, &tenant).await?;
    let finding = db
        .findings()
        .find_one(doc! { "_id": oid })
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
        .ok_or(StatusCode::NOT_FOUND)?;

    Ok(Json(ApiResponse {
        data: finding,
        total: None,
        page: None,
    }))
}

#[tracing::instrument(skip_all, fields(finding_id = %id))]
pub async fn update_finding_status(
    Extension(agent): AgentExt,
    tenant: TenantCtx,
    Path(id): Path<String>,
    Json(req): Json<UpdateStatusRequest>,
) -> 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?;

    db.findings()
        .update_one(
            doc! { "_id": oid },
            doc! { "$set": { "status": &req.status, "updated_at": mongodb::bson::DateTime::now() } },
        )
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok(Json(serde_json::json!({ "status": "updated" })))
}

#[tracing::instrument(skip_all)]
pub async fn bulk_update_finding_status(
    Extension(agent): AgentExt,
    tenant: TenantCtx,
    Json(req): Json<BulkUpdateStatusRequest>,
) -> Result<Json<serde_json::Value>, StatusCode> {
    let oids: Vec<mongodb::bson::oid::ObjectId> = req
        .ids
        .iter()
        .filter_map(|id| mongodb::bson::oid::ObjectId::parse_str(id).ok())
        .collect();

    if oids.is_empty() {
        return Err(StatusCode::BAD_REQUEST);
    }

    let db = tenant_db(&agent, &tenant).await?;
    let result = db
        .findings()
        .update_many(
            doc! { "_id": { "$in": oids } },
            doc! { "$set": { "status": &req.status, "updated_at": mongodb::bson::DateTime::now() } },
        )
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

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

#[tracing::instrument(skip_all)]
pub async fn update_finding_feedback(
    Extension(agent): AgentExt,
    tenant: TenantCtx,
    Path(id): Path<String>,
    Json(req): Json<UpdateFeedbackRequest>,
) -> 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?;

    db.findings()
        .update_one(
            doc! { "_id": oid },
            doc! { "$set": { "developer_feedback": &req.feedback, "updated_at": mongodb::bson::DateTime::now() } },
        )
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok(Json(serde_json::json!({ "status": "updated" })))
}
