use axum::{
    extract::{Path, State},
    http::StatusCode,
    routing::post,
    Json, Router,
};
use chrono::Utc;
use uuid::Uuid;

use crate::types::*;
use crate::AppState;

pub(crate) fn router() -> Router<AppState> {
    Router::new().route("/api/v1/projects/:project_id/import", post(import_data))
}

async fn import_data(
    State(state): State<AppState>,
    Path(project_id): Path<String>,
    Json(payload): Json<ImportRequest>,
) -> Result<Json<ApiResponse<ImportResult>>, StatusCode> {
    let pid = crate::resolve_project_id(&state.db, &project_id).await?;

    let mut nodes_created = 0;
    let mut nodes_skipped = 0;
    let mut edges_created = 0;
    let mut edges_skipped = 0;
    let mut errors: Vec<String> = Vec::new();

    // Deduplicate nodes by label
    let mut seen_labels: std::collections::HashMap<String, uuid::Uuid> =
        std::collections::HashMap::new();

    for n in &payload.nodes {
        let label = n.label.trim();
        if label.is_empty() {
            errors.push("Skipped node with empty label".into());
            continue;
        }
        if let Some(existing) = state
            .db
            .find_node_by_label(pid, label)
            .map_err(|err| crate::db_error_status("import.find_node_by_label", "node", err))?
        {
            seen_labels.insert(label.to_string(), existing.id);
            nodes_skipped += 1;
        } else {
            let node_type = n
                .node_type
                .as_ref()
                .and_then(|s| serde_json::from_str::<NodeType>(s).ok())
                .unwrap_or(NodeType::Note);
            let content = n.content.clone().unwrap_or_default();
            let mut props = n.properties.clone().unwrap_or_default();
            if !content.is_empty() && props.is_object() {
                if let serde_json::Value::Object(ref mut m) = props {
                    m.insert("content".into(), serde_json::Value::String(content));
                }
            } else if !content.is_empty() {
                props = serde_json::json!({ "content": content });
            }
            let node = Node {
                id: Uuid::new_v4(),
                project_id: pid,
                label: label.to_string(),
                node_type,
                position: None,
                properties: props,
                created_at: Utc::now(),
            };
            if state
                .db
                .insert_node(&node)
                .map_err(|err| crate::db_error_status("import.insert_node", "node", err))
                .is_ok()
            {
                seen_labels.insert(label.to_string(), node.id);
                nodes_created += 1;
                let _ = state.tx.send(format!("node_created:{pid}"));
            }
        }
    }

    for e in &payload.edges {
        let source_label = e.source_label.trim();
        let target_label = e.target_label.trim();
        if source_label.is_empty() || target_label.is_empty() {
            errors.push(format!("Skipped edge with empty source or target label"));
            continue;
        }
        let (Some(&source_id), Some(&target_id)) =
            (seen_labels.get(source_label), seen_labels.get(target_label))
        else {
            errors.push(format!(
                "Edge skipped: source or target node not found for '{}' -> '{}'",
                source_label, target_label
            ));
            edges_skipped += 1;
            continue;
        };
        let edge = Edge {
            id: Uuid::new_v4(),
            project_id: pid,
            source_node_id: source_id,
            target_node_id: target_id,
            label: e.label.clone(),
            created_at: Utc::now(),
        };
        if state
            .db
            .insert_edge(&edge)
            .map_err(|err| crate::db_error_status("import.insert_edge", "edge", err))
            .is_ok()
        {
            edges_created += 1;
            let _ = state.tx.send(format!("edge_created:{pid}"));
        }
    }

    let _ = state.tx.send(format!("graph:{pid}"));
    Ok(Json(ApiResponse::ok(ImportResult {
        nodes_created,
        nodes_skipped,
        edges_created,
        edges_skipped,
        errors,
    })))
}
