use std::collections::HashMap;
use std::sync::Arc;

use anyhow::{anyhow, Context, Result};
use axum::{
    body::Bytes,
    extract::{Path, State},
    http::{HeaderMap, StatusCode},
    response::Json,
};
use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use tracing::{error, info};

type HmacSha256 = Hmac<Sha256>;

/// In-memory workspace registration (in production: backed by SurrealDB).
#[derive(Debug, Clone)]
pub struct WorkspaceRegistration {
    pub workspace_id: String,
    pub git_remote: String,
    pub branch: String,
    /// HMAC-SHA256 key for webhook signature validation.
    pub webhook_secret: String,
    /// Local checkout path.
    pub local_path: String,
}

/// Registry of workspace registrations.
#[derive(Default)]
pub struct WorkspaceRegistry {
    registrations: HashMap<String, WorkspaceRegistration>,
}

impl WorkspaceRegistry {
    pub fn new() -> Self {
        Self {
            registrations: HashMap::new(),
        }
    }

    pub fn register(&mut self, reg: WorkspaceRegistration) {
        self.registrations.insert(reg.workspace_id.clone(), reg);
    }

    pub fn get(&self, workspace_id: &str) -> Option<&WorkspaceRegistration> {
        self.registrations.get(workspace_id)
    }
}

/// Shared handler state.
pub struct WebhookState {
    pub registry: Arc<parking_lot::RwLock<WorkspaceRegistry>>,
}

/// Incoming push event payload (Forgejo/Gitea/GitHub compatible subset).
#[derive(Debug, Deserialize)]
pub struct PushEvent {
    #[serde(rename = "ref")]
    pub git_ref: String,
    pub head_commit: Option<HeadCommit>,
}

#[derive(Debug, Deserialize)]
pub struct HeadCommit {
    pub id: String,
    pub message: String,
}

#[derive(Debug, Serialize)]
pub struct WebhookResponse {
    pub status: String,
    pub workspace_id: String,
    pub commit_sha: Option<String>,
}

/// POST /api/v1/webhooks/{workspace_id}
///
/// Validates HMAC-SHA256 signature, then triggers git pull + deployment.
pub async fn handle_webhook(
    State(state): State<Arc<WebhookState>>,
    Path(workspace_id): Path<String>,
    headers: HeaderMap,
    body: Bytes,
) -> Result<Json<WebhookResponse>, StatusCode> {
    // Look up registration
    let reg = {
        let registry = state.registry.read();
        match registry.get(&workspace_id) {
            Some(r) => r.clone(),
            None => {
                error!("Webhook for unknown workspace: {}", workspace_id);
                return Err(StatusCode::NOT_FOUND);
            }
        }
    };

    // Validate HMAC-SHA256 signature (X-Hub-Signature-256 header)
    if let Err(e) = validate_signature(&headers, &body, &reg.webhook_secret) {
        error!(workspace_id = %workspace_id, "Webhook signature validation failed: {}", e);
        return Err(StatusCode::UNAUTHORIZED);
    }

    // Parse push event
    let push_event: PushEvent = serde_json::from_slice(&body).map_err(|e| {
        error!("Failed to parse push event: {}", e);
        StatusCode::BAD_REQUEST
    })?;

    let commit_sha = push_event.head_commit.as_ref().map(|c| c.id.clone());

    // Check branch matches expected
    let expected_ref = format!("refs/heads/{}", reg.branch);
    if push_event.git_ref != expected_ref {
        info!(
            workspace_id = %workspace_id,
            git_ref = %push_event.git_ref,
            expected = %expected_ref,
            "Webhook for non-tracked branch — ignoring"
        );
        return Ok(Json(WebhookResponse {
            status: "ignored".to_string(),
            workspace_id,
            commit_sha,
        }));
    }

    info!(
        workspace_id = %workspace_id,
        commit_sha = ?commit_sha,
        "Webhook validated — triggering deployment"
    );

    // Trigger git pull in a separate task (non-blocking response)
    let local_path = reg.local_path.clone();
    let ws_id = workspace_id.clone();
    tokio::spawn(async move {
        if let Err(e) = super::git_executor::pull_and_deploy(&ws_id, &local_path).await {
            error!(workspace_id = %ws_id, "Git pull/deploy failed: {}", e);
        }
    });

    Ok(Json(WebhookResponse {
        status: "accepted".to_string(),
        workspace_id,
        commit_sha,
    }))
}

fn validate_signature(headers: &HeaderMap, body: &[u8], secret: &str) -> Result<()> {
    let sig_header = headers
        .get("X-Hub-Signature-256")
        .or_else(|| headers.get("x-hub-signature-256"))
        .ok_or_else(|| anyhow!("Missing X-Hub-Signature-256 header"))?
        .to_str()
        .context("X-Hub-Signature-256 is not valid UTF-8")?;

    let sig_hex = sig_header
        .strip_prefix("sha256=")
        .ok_or_else(|| anyhow!("X-Hub-Signature-256 must start with 'sha256='"))?;

    let expected = hex::decode(sig_hex).context("Invalid hex in signature")?;

    let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).context("HMAC key error")?;
    mac.update(body);

    mac.verify_slice(&expected)
        .map_err(|_| anyhow!("Signature mismatch"))
}
