use chrono::Utc;
use serde_json::Value;
use std::{path::Path, process::Command};
use thiserror::Error;
use uuid::Uuid;

#[derive(Error, Debug)]
pub enum GraphError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Command failed: {0}")]
    CommandFailed(String),
    #[error("Serde JSON error: {0}")]
    SerdeJson(#[from] serde_json::Error),
    #[error("Other error: {0}")]
    Other(String),
}

pub struct DoltGraph {
    repo_path: std::path::PathBuf,
}

impl DoltGraph {
    pub fn init(path: &Path) -> Result<Self, GraphError> {
        let status = Command::new("dolt")
            .arg("init")
            .current_dir(path)
            .status()?;
        if !status.success() {
            return Err(GraphError::CommandFailed(format!(
                "dolt init failed with status {:?}",
                status
            )));
        }
        Ok(Self {
            repo_path: path.to_path_buf(),
        })
    }

    fn run_cmd(&self, args: &[&str]) -> Result<(), GraphError> {
        let output = Command::new("dolt")
            .args(args)
            .current_dir(&self.repo_path)
            .output()?;
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(GraphError::CommandFailed(stderr.to_string()));
        }
        Ok(())
    }

    pub fn commit(&self, message: &str) -> Result<(), GraphError> {
        self.run_cmd(&["add", "."])?;
        self.run_cmd(&["commit", "-m", message])?;
        Ok(())
    }

    pub fn create_table(&self, table_name: &str, schema: &str) -> Result<(), GraphError> {
        let query = format!("CREATE TABLE {} ({})", table_name, schema);
        self.run_cmd(&["sql", "-q", &query])?;
        Ok(())
    }

    pub fn insert_node(&self, table: &str, data: Value) -> Result<(), GraphError> {
        let obj = data
            .as_object()
            .ok_or_else(|| GraphError::Other("Data must be a JSON object".into()))?;
        let columns: Vec<String> = obj.keys().cloned().collect();
        let mut values: Vec<String> = Vec::new();
        for key in &columns {
            let v = &obj[key];
            let sql_val = match v {
                Value::String(s) => format!("'{}'", s.replace('\'', "''")),
                Value::Number(n) => n.to_string(),
                Value::Bool(b) => {
                    if *b {
                        "TRUE".into()
                    } else {
                        "FALSE".into()
                    }
                }
                Value::Null => "NULL".into(),
                _ => return Err(GraphError::Other("Unsupported JSON value type".into())),
            };
            values.push(sql_val);
        }
        let query = format!(
            "INSERT INTO {} ({}) VALUES ({})",
            table,
            columns.join(", "),
            values.join(", ")
        );
        self.run_cmd(&["sql", "-q", &query])?;
        Ok(())
    }
}

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

    #[test]
    fn test_init_create_table_and_commit() {
        let dir = TempDir::new().unwrap();
        let graph = DoltGraph::init(dir.path()).expect("init failed");
        graph.create_table("nodes", "id INT PRIMARY KEY, name TEXT").expect("create table failed");
        graph.commit("initial commit with table").expect("commit failed");
    }
}
