use std::sync::Arc;

use axum::body::Bytes;
use axum::extract::{Extension, Path};
use axum::http::{HeaderMap, StatusCode};

use compliance_core::models::ScanTrigger;

use crate::agent::ComplianceAgent;

pub async fn handle_gitlab_webhook(
    Extension(agent): Extension<Arc<ComplianceAgent>>,
    Path((tenant_id, repo_id)): Path<(String, String)>,
    headers: HeaderMap,
    body: Bytes,
) -> StatusCode {
    // Look up the repo in the tenant's database to get its webhook secret
    let oid = match mongodb::bson::oid::ObjectId::parse_str(&repo_id) {
        Ok(oid) => oid,
        Err(_) => return StatusCode::NOT_FOUND,
    };
    let db = match agent.db_pool.for_tenant_id(&tenant_id).await {
        Ok(db) => db,
        Err(e) => {
            tracing::warn!("GitLab webhook: cannot open tenant database '{tenant_id}': {e}");
            return StatusCode::NOT_FOUND;
        }
    };
    let repo = match db
        .onboarded_targets()
        .find_one(mongodb::bson::doc! { "_id": oid })
        .await
    {
        Ok(Some(repo)) => repo,
        _ => {
            tracing::warn!("GitLab webhook: repo {repo_id} not found in tenant '{tenant_id}'");
            return StatusCode::NOT_FOUND;
        }
    };

    // GitLab sends the secret token in X-Gitlab-Token header (plain text comparison)
    if let Some(secret) = &repo.webhook_secret {
        let token = headers
            .get("x-gitlab-token")
            .and_then(|v| v.to_str().ok())
            .unwrap_or("");

        if token != secret {
            tracing::warn!("GitLab webhook: invalid token for repo {repo_id}");
            return StatusCode::UNAUTHORIZED;
        }
    }

    let payload: serde_json::Value = match serde_json::from_slice(&body) {
        Ok(v) => v,
        Err(e) => {
            tracing::warn!("GitLab webhook: invalid JSON: {e}");
            return StatusCode::BAD_REQUEST;
        }
    };

    let event_type = payload["object_kind"].as_str().unwrap_or("");

    match event_type {
        "push" => {
            let agent_clone = (*agent).clone();
            let repo_id = repo_id.clone();
            let tenant_id = tenant_id.clone();
            tokio::spawn(async move {
                tracing::info!(
                    "GitLab push webhook: triggering scan for {repo_id} in tenant {tenant_id}"
                );
                if let Err(e) = agent_clone
                    .run_scan(&tenant_id, &repo_id, ScanTrigger::Webhook)
                    .await
                {
                    tracing::error!("Webhook-triggered scan failed: {e}");
                }
            });
            StatusCode::OK
        }
        "merge_request" => handle_merge_request(agent, &tenant_id, &repo_id, &payload).await,
        _ => {
            tracing::debug!("GitLab webhook: ignoring event '{event_type}'");
            StatusCode::OK
        }
    }
}

async fn handle_merge_request(
    agent: Arc<ComplianceAgent>,
    tenant_id: &str,
    repo_id: &str,
    payload: &serde_json::Value,
) -> StatusCode {
    let action = payload["object_attributes"]["action"]
        .as_str()
        .unwrap_or("");
    if action != "open" && action != "update" {
        return StatusCode::OK;
    }

    let mr_iid = payload["object_attributes"]["iid"].as_u64().unwrap_or(0);
    let head_sha = payload["object_attributes"]["last_commit"]["id"]
        .as_str()
        .unwrap_or("");
    let base_sha = payload["object_attributes"]["diff_refs"]["base_sha"]
        .as_str()
        .unwrap_or("");

    if mr_iid == 0 || head_sha.is_empty() || base_sha.is_empty() {
        tracing::warn!("GitLab MR webhook: missing required fields");
        return StatusCode::BAD_REQUEST;
    }

    let repo_id = repo_id.to_string();
    let tenant_id = tenant_id.to_string();
    let head_sha = head_sha.to_string();
    let base_sha = base_sha.to_string();
    let agent_clone = (*agent).clone();
    tokio::spawn(async move {
        tracing::info!("GitLab MR webhook: reviewing MR !{mr_iid} on {repo_id}");
        if let Err(e) = agent_clone
            .run_pr_review(&tenant_id, &repo_id, mr_iid, &base_sha, &head_sha)
            .await
        {
            tracing::error!("MR review failed for !{mr_iid}: {e}");
        }
    });

    StatusCode::OK
}
