use std::sync::Arc;

use axum::body::Bytes;
use axum::extract::{Extension, Path};
use axum::http::{HeaderMap, StatusCode};
use hmac::{Hmac, Mac};
use sha2::Sha256;

use compliance_core::models::ScanTrigger;

use crate::agent::ComplianceAgent;

type HmacSha256 = Hmac<Sha256>;

pub async fn handle_github_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!("GitHub 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!("GitHub webhook: repo {repo_id} not found in tenant '{tenant_id}'");
            return StatusCode::NOT_FOUND;
        }
    };

    // Verify HMAC-SHA256 signature using the per-repo secret
    if let Some(secret) = &repo.webhook_secret {
        let signature = headers
            .get("x-hub-signature-256")
            .and_then(|v| v.to_str().ok())
            .unwrap_or("");

        if !verify_signature(secret, &body, signature) {
            tracing::warn!("GitHub webhook: invalid signature for repo {repo_id}");
            return StatusCode::UNAUTHORIZED;
        }
    }

    let event = headers
        .get("x-github-event")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");

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

    match event {
        "push" => {
            let agent_clone = (*agent).clone();
            let repo_id = repo_id.clone();
            let tenant_id = tenant_id.clone();
            tokio::spawn(async move {
                tracing::info!(
                    "GitHub 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
        }
        "pull_request" => handle_pull_request(agent, &tenant_id, &repo_id, &payload).await,
        _ => {
            tracing::debug!("GitHub webhook: ignoring event '{event}'");
            StatusCode::OK
        }
    }
}

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

    let pr_number = payload["pull_request"]["number"].as_u64().unwrap_or(0);
    let head_sha = payload["pull_request"]["head"]["sha"]
        .as_str()
        .unwrap_or("");
    let base_sha = payload["pull_request"]["base"]["sha"]
        .as_str()
        .unwrap_or("");

    if pr_number == 0 || head_sha.is_empty() || base_sha.is_empty() {
        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!("GitHub PR webhook: reviewing PR #{pr_number} on {repo_id}");
        if let Err(e) = agent_clone
            .run_pr_review(&tenant_id, &repo_id, pr_number, &base_sha, &head_sha)
            .await
        {
            tracing::error!("PR review failed for #{pr_number}: {e}");
        }
    });

    StatusCode::OK
}

fn verify_signature(secret: &str, body: &[u8], signature: &str) -> bool {
    // GitHub sends sha256=<hex>
    let sig = signature.strip_prefix("sha256=").unwrap_or(signature);
    let sig_bytes = match hex::decode(sig) {
        Ok(b) => b,
        Err(_) => return false,
    };

    let mut mac = match HmacSha256::new_from_slice(secret.as_bytes()) {
        Ok(m) => m,
        Err(_) => return false,
    };
    mac.update(body);
    mac.verify_slice(&sig_bytes).is_ok()
}
