use crate::database::Database;
use std::sync::Arc;

pub mod compiler;

use dashmap::DashMap;

pub struct Queryer {
  pub db: Arc<Database>,
  cache: DashMap<String, String>,
}

impl Queryer {
  pub fn new(db: Arc<Database>) -> Self {
    Self {
      db,
      cache: DashMap::new(),
    }
  }

  pub fn query(
    &self,
    schema_id: &str,
    stem_opt: Option<&str>,
    filters: Option<&serde_json::Value>,
  ) -> crate::drop::Drop {
    let filters_map: Option<&serde_json::Map<String, serde_json::Value>> =
      filters.and_then(|f| f.as_object());

    // Generate Permutation Cache Key: schema_id + sorted filter keys
    let mut filter_keys: Vec<String> = Vec::new();
    if let Some(fm) = filters_map {
      for key in fm.keys() {
        filter_keys.push(key.clone());
      }
    }
    filter_keys.sort();

    let stem_key = stem_opt.unwrap_or("/");
    let cache_key = format!("{}(Stem:{}):{}", schema_id, stem_key, filter_keys.join(","));

    let sql = if let Some(cached_sql) = self.cache.get(&cache_key) {
      cached_sql.value().clone()
    } else {
      // Compile the massive base SQL string
      let compiler = compiler::SqlCompiler::new(self.db.clone());
      match compiler.compile(schema_id, stem_opt, &filter_keys) {
        Ok(compiled_sql) => {
          self.cache.insert(cache_key.clone(), compiled_sql.clone());
          compiled_sql
        }
        Err(e) => {
          return crate::drop::Drop::with_errors(vec![crate::drop::Error {
            code: "QUERY_COMPILATION_FAILED".to_string(),
            message: e,
            details: crate::drop::ErrorDetails {
              path: schema_id.to_string(),
            },
          }]);
        }
      }
    };

    // 2. Prepare the execution arguments from the filters
    let mut args: Vec<serde_json::Value> = Vec::new();

    if let Some(fm) = filters_map {
      for (_i, key) in filter_keys.iter().enumerate() {
        if let Some(val) = fm.get(key) {
          args.push(val.clone());
        }
      }
    }

    // 3. Execute via Database Executor
    match self.db.query(&sql, Some(&args)) {
      Ok(serde_json::Value::Array(table)) => {
        if table.is_empty() {
          crate::drop::Drop::success_with_val(serde_json::Value::Null)
        } else {
          // We expect the query to return a single JSONB column, already unpacked from row[0]
          crate::drop::Drop::success_with_val(table.first().unwrap().clone())
        }
      }
      Ok(other) => crate::drop::Drop::with_errors(vec![crate::drop::Error {
        code: "QUERY_FAILED".to_string(),
        message: format!("Expected array from generic query, got: {:?}", other),
        details: crate::drop::ErrorDetails {
          path: schema_id.to_string(),
        },
      }]),
      Err(e) => crate::drop::Drop::with_errors(vec![crate::drop::Error {
        code: "QUERY_FAILED".to_string(),
        message: format!("SPI error in queryer: {}", e),
        details: crate::drop::ErrorDetails {
          path: schema_id.to_string(),
        },
      }]),
    }
  }
}
