use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::sync::{Arc, OnceLock, RwLock};

#[derive(Debug, Deserialize)]
pub struct TestSuite {
  #[allow(dead_code)]
  pub description: String,
  pub database: serde_json::Value,
  pub tests: Vec<TestCase>,
}

use crate::tests::types::TestCase;
use serde_json::Value;

pub fn deserialize_some<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
where
  D: serde::Deserializer<'de>,
{
  let v = Value::deserialize(deserializer)?;
  Ok(Some(v))
}

// Type alias for easier reading
type CompiledSuite = Arc<Vec<(TestSuite, Arc<crate::database::Database>)>>;

// Global cache mapping filename -> Vector of (Parsed JSON suite, Compiled Database)
static CACHE: OnceLock<RwLock<HashMap<String, CompiledSuite>>> = OnceLock::new();

fn get_cached_file(path: &str) -> CompiledSuite {
  let cache_lock = CACHE.get_or_init(|| RwLock::new(HashMap::new()));

  let file_data = {
    let read_guard = cache_lock.read().unwrap();
    read_guard.get(path).cloned()
  };

  match file_data {
    Some(data) => data,
    None => {
      let mut write_guard = cache_lock.write().unwrap();
      // double check in case another thread compiled while we waited for lock
      if let Some(data) = write_guard.get(path) {
        data.clone()
      } else {
        let content =
          fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read file: {}", path));
        let suites: Vec<TestSuite> = serde_json::from_str(&content)
          .unwrap_or_else(|e| panic!("Failed to parse JSON in {}: {}", path, e));

        let mut compiled_suites = Vec::new();
        for suite in suites {
          let db_result = crate::database::Database::new(&suite.database);
          if let Err(drop) = db_result {
            let error_messages: Vec<String> = drop
              .errors
              .into_iter()
              .map(|e| format!("Error {} at path {}: {}", e.code, e.details.path, e.message))
              .collect();
            panic!(
              "System Setup Compilation failed for {}:\n{}",
              path,
              error_messages.join("\n")
            );
          }
          compiled_suites.push((suite, Arc::new(db_result.unwrap())));
        }

        let new_data = Arc::new(compiled_suites);
        write_guard.insert(path.to_string(), new_data.clone());
        new_data
      }
    }
  }
}

pub fn run_test_case(path: &str, suite_idx: usize, case_idx: usize) -> Result<(), String> {
  let file_data = get_cached_file(path);

  if suite_idx >= file_data.len() {
    panic!("Suite Index {} out of bounds for file {}", suite_idx, path);
  }

  let (group, db) = &file_data[suite_idx];

  if case_idx >= group.tests.len() {
    panic!(
      "Case Index {} out of bounds for suite {} in file {}",
      case_idx, suite_idx, path
    );
  }

  let test = &group.tests[case_idx];
  let mut failures = Vec::<String>::new();

  // 4. Run Tests
  // Provide fallback for legacy expectations if `expect` block was missing despite migration script
  let _expected_success = test
    .expect
    .as_ref()
    .map(|e| e.success)
    .unwrap_or(test.valid.unwrap_or(false));
  let _expected_errors = test
    .expect
    .as_ref()
    .and_then(|e| e.errors.clone())
    .unwrap_or(test.expect_errors.clone().unwrap_or(vec![]));

  match test.action.as_str() {
    "validate" => {
      let result = test.run_validate(db.clone());
      if let Err(e) = result {
        println!("TEST VALIDATE ERROR FOR '{}': {}", test.description, e);
        failures.push(format!(
          "[{}] Validate Test '{}' failed. Error: {}",
          group.description, test.description, e
        ));
      }
    }
    "merge" => {
      let result = test.run_merge(db.clone());
      if let Err(e) = result {
        println!("TEST MERGE ERROR FOR '{}': {}", test.description, e);
        failures.push(format!(
          "[{}] Merge Test '{}' failed. Error: {}",
          group.description, test.description, e
        ));
      }
    }
    "query" => {
      let result = test.run_query(db.clone());
      if let Err(e) = result {
        println!("TEST QUERY ERROR FOR '{}': {}", test.description, e);
        failures.push(format!(
          "[{}] Query Test '{}' failed. Error: {}",
          group.description, test.description, e
        ));
      }
    }
    _ => {
      failures.push(format!(
        "[{}] Unknown action '{}' for test '{}'",
        group.description, test.action, test.description
      ));
    }
  }

  if !failures.is_empty() {
    return Err(failures.join("\n"));
  }

  Ok(())
}
