use super::expect::ExpectBlock;
use crate::database::Database;
use serde::Deserialize;
use serde_json::Value;
use std::sync::Arc;

#[derive(Debug, Deserialize)]
pub struct TestCase {
  pub description: String,

  #[serde(default = "default_action")]
  pub action: String, // "validate", "merge", or "query"

  // For Validate & Query
  #[serde(default)]
  pub schema_id: String,

  // For Query
  #[serde(default)]
  pub stem: Option<String>,

  #[serde(default)]
  pub filters: Option<serde_json::Value>,

  // For Merge & Validate
  #[serde(default)]
  pub data: Option<serde_json::Value>,

  // For Merge & Query mocks
  #[serde(default)]
  pub mocks: Option<serde_json::Value>,

  pub expect: Option<ExpectBlock>,

  // Legacy support for older tests to avoid migrating them all instantly
  pub valid: Option<bool>,
  pub expect_errors: Option<Vec<serde_json::Value>>,
}

fn default_action() -> String {
  "validate".to_string()
}

impl TestCase {
  pub fn execute(&self, db: Arc<Database>) -> Result<(), String> {
    match self.action.as_str() {
      "validate" => self.run_validate(db),
      "merge" => self.run_merge(db),
      "query" => self.run_query(db),
      _ => Err(format!(
        "Unknown action '{}' for test '{}'",
        self.action, self.description
      )),
    }
  }

  pub fn run_validate(&self, db: Arc<Database>) -> Result<(), String> {
    use crate::validator::Validator;

    let validator = Validator::new(db);

    let expected_success = self
      .expect
      .as_ref()
      .map(|e| e.success)
      .unwrap_or(self.valid.unwrap_or(false));

    // _expected_errors is preserved for future diffing if needed
    let _expected_errors = self
      .expect
      .as_ref()
      .and_then(|e| e.errors.clone())
      .unwrap_or(self.expect_errors.clone().unwrap_or(vec![]));

    let schema_id = &self.schema_id;
    if !validator.db.schemas.contains_key(schema_id) {
      return Err(format!(
        "Missing Schema: Cannot find schema ID '{}'",
        schema_id
      ));
    }

    let test_data = self.data.clone().unwrap_or(Value::Null);
    let result = validator.validate(schema_id, &test_data);

    let got_valid = result.errors.is_empty();

    if got_valid != expected_success {
      let error_msg = if result.errors.is_empty() {
        "None".to_string()
      } else {
        format!("{:?}", result.errors)
      };

      return Err(format!(
        "Expected: {}, Got: {}. Errors: {}",
        expected_success, got_valid, error_msg
      ));
    }

    Ok(())
  }

  pub fn run_merge(&self, db: Arc<Database>) -> Result<(), String> {
    use crate::merger::Merger;
    let merger = Merger::new(db.clone());

    let test_data = self.data.clone().unwrap_or(Value::Null);
    let result = merger.merge(test_data);

    let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false);
    let got_success = result.errors.is_empty();

    let error_msg = if result.errors.is_empty() {
      "None".to_string()
    } else {
      format!("{:?}", result.errors)
    };

    let return_val = if expected_success != got_success {
      Err(format!(
        "Merge Expected: {}, Got: {}. Errors: {}",
        expected_success, got_success, error_msg
      ))
    } else if let Some(expect) = &self.expect {
      let queries = db.executor.get_queries();
      expect.assert_sql(&queries)
    } else {
      Ok(())
    };

    db.executor.reset_mocks();
    return_val
  }

  pub fn run_query(&self, db: Arc<Database>) -> Result<(), String> {
    use crate::queryer::Queryer;
    let queryer = Queryer::new(db.clone());

    let stem_opt = self.stem.as_deref();
    let result = queryer.query(&self.schema_id, stem_opt, self.filters.as_ref());

    let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false);
    let got_success = result.errors.is_empty();

    let error_msg = if result.errors.is_empty() {
      "None".to_string()
    } else {
      format!("{:?}", result.errors)
    };

    let return_val = if expected_success != got_success {
      Err(format!(
        "Query Expected: {}, Got: {}. Errors: {}",
        expected_success, got_success, error_msg
      ))
    } else if let Some(expect) = &self.expect {
      let queries = db.executor.get_queries();
      expect.assert_sql(&queries)
    } else {
      Ok(())
    };

    db.executor.reset_mocks();
    return_val
  }
}
