// src/table_script/handlers/dependency_utils.rs
// Utility functions for dependency analysis and debugging

use sqlx::PgPool;
use serde_json::{json, Value};
use std::collections::HashMap;

/// Get a visual representation of the dependency graph for a schema
pub async fn get_dependency_graph_visualization(
    db_pool: &PgPool,
    schema_id: i64,
) -> Result<Value, sqlx::Error> {
    let dependencies = sqlx::query!(
        r#"SELECT 
            st.table_name as source_table,
            tt.table_name as target_table,
            sd.dependency_type,
            sd.context_info,
            COUNT(*) as dependency_count
           FROM script_dependencies sd
           JOIN table_definitions st ON sd.source_table_id = st.id
           JOIN table_definitions tt ON sd.target_table_id = tt.id
           WHERE st.schema_id = $1
           GROUP BY st.table_name, tt.table_name, sd.dependency_type, sd.context_info
           ORDER BY st.table_name, tt.table_name"#,
        schema_id
    )
    .fetch_all(db_pool)
    .await?;

    let mut graph = HashMap::new();
    for dep in dependencies {
        let source_entry = graph.entry(dep.source_table.clone()).or_insert_with(|| json!({
            "table": dep.source_table,
            "dependencies": []
        }));
        
        if let Some(deps_array) = source_entry.get_mut("dependencies") {
            if let Some(deps) = deps_array.as_array_mut() {
                deps.push(json!({
                    "target": dep.target_table,
                    "type": dep.dependency_type,
                    "context": dep.context_info,
                    "count": dep.dependency_count.unwrap_or(0)
                }));
            }
        }
    }

    Ok(json!({
        "schema_id": schema_id,
        "dependency_graph": graph.into_values().collect::<Vec<_>>()
    }))
}

/// Check if a schema has any circular dependencies
pub async fn check_schema_for_cycles(
    db_pool: &PgPool,
    schema_id: i64,
) -> Result<Vec<String>, sqlx::Error> {
    // This is a simplified cycle detection for monitoring purposes
    let dependencies = sqlx::query!(
        r#"SELECT sd.source_table_id, sd.target_table_id, st.table_name as source_table, tt.table_name as target_table
           FROM script_dependencies sd
           JOIN table_definitions st ON sd.source_table_id = st.id
           JOIN table_definitions tt ON sd.target_table_id = tt.id
           WHERE st.schema_id = $1"#,
        schema_id
    )
    .fetch_all(db_pool)
    .await?;

    let mut graph: HashMap<i64, Vec<i64>> = HashMap::new();
    let mut table_names: HashMap<i64, String> = HashMap::new();
    
    for dep in dependencies {
        graph.entry(dep.source_table_id).or_default().push(dep.target_table_id);
        table_names.insert(dep.source_table_id, dep.source_table);
        table_names.insert(dep.target_table_id, dep.target_table);
    }

    // Simple cycle detection using DFS
    let mut visited = std::collections::HashSet::new();
    let mut rec_stack = std::collections::HashSet::new();
    let mut cycles = Vec::new();

    for &node in graph.keys() {
        if !visited.contains(&node) {
            if let Some(cycle) = detect_cycle_dfs(
                node, 
                &graph, 
                &mut visited, 
                &mut rec_stack, 
                &table_names
            ) {
                cycles.push(cycle);
            }
        }
    }

    Ok(cycles)
}

fn detect_cycle_dfs(
    node: i64,
    graph: &HashMap<i64, Vec<i64>>,
    visited: &mut std::collections::HashSet<i64>,
    rec_stack: &mut std::collections::HashSet<i64>,
    table_names: &HashMap<i64, String>,
) -> Option<String> {
    visited.insert(node);
    rec_stack.insert(node);

    if let Some(neighbors) = graph.get(&node) {
        for &neighbor in neighbors {
            if !visited.contains(&neighbor) {
                if let Some(cycle) = detect_cycle_dfs(neighbor, graph, visited, rec_stack, table_names) {
                    return Some(cycle);
                }
            } else if rec_stack.contains(&neighbor) {
                // Found a cycle
                let node_name = table_names.get(&node).cloned().unwrap_or_else(|| node.to_string());
                let neighbor_name = table_names.get(&neighbor).cloned().unwrap_or_else(|| neighbor.to_string());
                return Some(format!("{} -> {}", node_name, neighbor_name));
            }
        }
    }

    rec_stack.remove(&node);
    None
}

/// Get processing order for tables based on dependencies
/// Tables with no dependencies come first, then tables that depend only on already-processed tables
pub async fn get_table_processing_order(
    db_pool: &PgPool,
    schema_id: i64,
) -> Result<Vec<String>, sqlx::Error> {
    let all_tables = sqlx::query!(
        "SELECT id, table_name FROM table_definitions WHERE schema_id = $1 ORDER BY table_name",
        schema_id
    )
    .fetch_all(db_pool)
    .await?;

    let dependencies = sqlx::query!(
        r#"SELECT source_table_id, target_table_id
           FROM script_dependencies sd
           JOIN table_definitions td ON sd.source_table_id = td.id
           WHERE td.schema_id = $1"#,
        schema_id
    )
    .fetch_all(db_pool)
    .await?;

    // Build dependency graph
    let mut graph: HashMap<i64, Vec<i64>> = HashMap::new();
    let mut in_degree: HashMap<i64, usize> = HashMap::new();
    let mut table_names: HashMap<i64, String> = HashMap::new();

    // Initialize all tables
    for table in &all_tables {
        in_degree.insert(table.id, 0);
        table_names.insert(table.id, table.table_name.clone());
        graph.insert(table.id, Vec::new());
    }

    // Build graph and calculate in-degrees
    for dep in dependencies {
        graph.entry(dep.target_table_id).or_default().push(dep.source_table_id);
        *in_degree.entry(dep.source_table_id).or_insert(0) += 1;
    }

    // Topological sort using Kahn's algorithm
    let mut queue: Vec<i64> = in_degree
        .iter()
        .filter(|(_, &degree)| degree == 0)
        .map(|(&id, _)| id)
        .collect();
    
    let mut result = Vec::new();

    while let Some(node) = queue.pop() {
        result.push(table_names[&node].clone());

        if let Some(neighbors) = graph.get(&node) {
            for &neighbor in neighbors {
                if let Some(degree) = in_degree.get_mut(&neighbor) {
                    *degree -= 1;
                    if *degree == 0 {
                        queue.push(neighbor);
                    }
                }
            }
        }
    }

    // If result doesn't contain all tables, there are cycles
    if result.len() != all_tables.len() {
        return Err(sqlx::Error::Protocol("Circular dependencies detected - cannot determine processing order".into()));
    }

    Ok(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cycle_detection_simple() {
        // Test the cycle detection logic with a simple case
        let mut graph = HashMap::new();
        graph.insert(1, vec![2]);
        graph.insert(2, vec![1]); // 1 -> 2 -> 1 (cycle)
        
        let mut table_names = HashMap::new();
        table_names.insert(1, "table_a".to_string());
        table_names.insert(2, "table_b".to_string());
        
        let mut visited = std::collections::HashSet::new();
        let mut rec_stack = std::collections::HashSet::new();
        
        let cycle = detect_cycle_dfs(1, &graph, &mut visited, &mut rec_stack, &table_names);
        assert!(cycle.is_some());
        let cycle_str = cycle.unwrap();
        assert!(cycle_str.contains("table_a") && cycle_str.contains("table_b"));
    }
}
