use crate::sparql_update::is_sparql_update;
use crate::QueryError;
use ontocore_catalog::OntologyCatalog;
use ontocore_core::limits::{MAX_QUERY_BYTES, MAX_SPARQL_RESULT_ROWS};
use oxigraph::sparql::{QueryResults, QuerySolution};
use serde::Serialize;
use std::collections::BTreeMap;

pub type Result<T> = std::result::Result<T, QueryError>;

#[derive(Debug, Clone, Serialize)]
pub struct SparqlResult {
    pub columns: Vec<String>,
    pub rows: Vec<BTreeMap<String, String>>,
    pub truncated: bool,
}

pub fn run_sparql(catalog: &OntologyCatalog, sparql: &str) -> Result<SparqlResult> {
    if sparql.len() > MAX_QUERY_BYTES {
        return Err(QueryError::Sparql(format!(
            "query exceeds maximum length of {MAX_QUERY_BYTES} bytes"
        )));
    }

    if is_sparql_update(sparql) {
        return Err(QueryError::Sparql(
            "SPARQL update operations are not supported; use read-only queries".to_string(),
        ));
    }

    let results = catalog.store().query(sparql).map_err(|e| QueryError::Sparql(e.to_string()))?;

    match results {
        QueryResults::Solutions(solutions) => {
            let columns: Vec<String> =
                solutions.variables().iter().map(|v| v.as_str().to_string()).collect();
            let mut rows = Vec::new();
            let mut truncated = false;
            for solution in solutions {
                if rows.len() >= MAX_SPARQL_RESULT_ROWS {
                    truncated = true;
                    break;
                }
                let solution = solution.map_err(|e| QueryError::Sparql(e.to_string()))?;
                rows.push(solution_to_row(&solution, &columns));
            }
            Ok(SparqlResult { columns, rows, truncated })
        }
        QueryResults::Boolean(value) => {
            let mut row = BTreeMap::new();
            row.insert("boolean".into(), value.to_string());
            Ok(SparqlResult { columns: vec!["boolean".into()], rows: vec![row], truncated: false })
        }
        QueryResults::Graph(_) => Err(QueryError::Sparql(
            "CONSTRUCT/DESCRIBE graph results are not supported in v0.1".to_string(),
        )),
    }
}

fn solution_to_row(solution: &QuerySolution, columns: &[String]) -> BTreeMap<String, String> {
    let mut row = BTreeMap::new();
    for col in columns {
        let value = solution.get(col.as_str()).map(|term| term.to_string()).unwrap_or_default();
        row.insert(col.clone(), value);
    }
    row
}

pub fn to_json(result: &SparqlResult) -> Result<String> {
    serde_json::to_string_pretty(result).map_err(|e| QueryError::Export(e.to_string()))
}
