use std::collections::HashSet;

use serde_json::Value;

use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;

impl<'a> ValidationContext<'a> {
  pub(crate) fn validate_object(
    &self,
    result: &mut ValidationResult,
  ) -> Result<bool, ValidationError> {
    let current = self.instance;
    if let Some(obj) = current.as_object() {
      // Entity Bound Implicit Type Validation
      if let Some(lookup_key) = self.schema.id.as_ref().or(self.schema.r#ref.as_ref()) {
        let base_type_name = lookup_key.split('.').next_back().unwrap_or("").to_string();
        if let Some(type_def) = self.db.types.get(&base_type_name)
          && let Some(type_val) = obj.get("type")
          && let Some(type_str) = type_val.as_str()
        {
          if type_def.variations.contains(type_str) {
            // Ensure it passes strict mode
            result.evaluated_keys.insert("type".to_string());
          } else {
            result.errors.push(ValidationError {
              code: "CONST_VIOLATED".to_string(), // Aligning with original const override errors
              message: format!(
                "Type '{}' is not a valid descendant for this entity bound schema",
                type_str
              ),
              path: format!("{}/type", self.path),
            });
          }
        }
      }
      if let Some(min) = self.schema.min_properties
        && (obj.len() as f64) < min
      {
        result.errors.push(ValidationError {
          code: "MIN_PROPERTIES".to_string(),
          message: "Too few properties".to_string(),
          path: self.path.to_string(),
        });
      }
      if let Some(max) = self.schema.max_properties
        && (obj.len() as f64) > max
      {
        result.errors.push(ValidationError {
          code: "MAX_PROPERTIES".to_string(),
          message: "Too many properties".to_string(),
          path: self.path.to_string(),
        });
      }
      if let Some(ref req) = self.schema.required {
        for field in req {
          if !obj.contains_key(field) {
            result.errors.push(ValidationError {
              code: "REQUIRED_FIELD_MISSING".to_string(),
              message: format!("Missing {}", field),
              path: format!("{}/{}", self.path, field),
            });
          }
        }
      }

      if let Some(ref deps) = self.schema.dependencies {
        for (prop, dep) in deps {
          if obj.contains_key(prop) {
            match dep {
              crate::database::schema::Dependency::Props(required_props) => {
                for req_prop in required_props {
                  if !obj.contains_key(req_prop) {
                    result.errors.push(ValidationError {
                      code: "DEPENDENCY_MISSING".to_string(),
                      message: format!("Property '{}' requires property '{}'", prop, req_prop),
                      path: self.path.to_string(),
                    });
                  }
                }
              }
              crate::database::schema::Dependency::Schema(dep_schema) => {
                let derived = self.derive_for_schema(dep_schema, false);
                let dep_res = derived.validate()?;
                result.evaluated_keys.extend(dep_res.evaluated_keys.clone());
                result.merge(dep_res);
              }
            }
          }
        }
      }

      if let Some(props) = &self.schema.properties {
        for (key, sub_schema) in props {
          if self.overrides.contains(key) {
            continue; // Skip validation if exactly this property was overridden by a child
          }

          if let Some(child_instance) = obj.get(key) {
            let new_path = format!("{}/{}", self.path, key);
            let is_ref = sub_schema.r#ref.is_some();
            let next_extensible = if is_ref { false } else { self.extensible };

            let derived = self.derive(
              sub_schema,
              child_instance,
              &new_path,
              HashSet::new(),
              next_extensible,
              false,
            );
            let mut item_res = derived.validate()?;

            // Entity Bound Implicit Type Interception
            if key == "type"
              && let Some(lookup_key) = sub_schema.id.as_ref().or(sub_schema.r#ref.as_ref())
            {
              let base_type_name = lookup_key.split('.').next_back().unwrap_or("").to_string();
              if let Some(type_def) = self.db.types.get(&base_type_name)
                && let Some(instance_type) = child_instance.as_str()
                && type_def.variations.contains(instance_type)
              {
                item_res
                  .errors
                  .retain(|e| e.code != "CONST_VIOLATED" && e.code != "ENUM_VIOLATED");
              }
            }

            result.merge(item_res);
            result.evaluated_keys.insert(key.to_string());
          }
        }
      }

      if let Some(ref compiled_pp) = self.schema.compiled_pattern_properties {
        for (compiled_re, sub_schema) in compiled_pp {
          for (key, child_instance) in obj {
            if compiled_re.0.is_match(key) {
              let new_path = format!("{}/{}", self.path, key);
              let is_ref = sub_schema.r#ref.is_some();
              let next_extensible = if is_ref { false } else { self.extensible };

              let derived = self.derive(
                sub_schema,
                child_instance,
                &new_path,
                HashSet::new(),
                next_extensible,
                false,
              );
              let item_res = derived.validate()?;
              result.merge(item_res);
              result.evaluated_keys.insert(key.to_string());
            }
          }
        }
      }

      if let Some(ref additional_schema) = self.schema.additional_properties {
        for (key, child_instance) in obj {
          let mut locally_matched = false;
          if let Some(props) = &self.schema.properties
            && props.contains_key(&key.to_string())
          {
            locally_matched = true;
          }
          if !locally_matched && let Some(ref compiled_pp) = self.schema.compiled_pattern_properties
          {
            for (compiled_re, _) in compiled_pp {
              if compiled_re.0.is_match(key) {
                locally_matched = true;
                break;
              }
            }
          }

          if !locally_matched {
            let new_path = format!("{}/{}", self.path, key);
            let is_ref = additional_schema.r#ref.is_some();
            let next_extensible = if is_ref { false } else { self.extensible };

            let derived = self.derive(
              additional_schema,
              child_instance,
              &new_path,
              HashSet::new(),
              next_extensible,
              false,
            );
            let item_res = derived.validate()?;
            result.merge(item_res);
            result.evaluated_keys.insert(key.to_string());
          }
        }
      }

      if let Some(ref property_names) = self.schema.property_names {
        for key in obj.keys() {
          let _new_path = format!("{}/propertyNames/{}", self.path, key);
          let val_str = Value::String(key.to_string());

          let ctx = ValidationContext::new(
            self.db,
            self.root,
            property_names,
            &val_str,
            HashSet::new(),
            self.extensible,
            self.reporter,
          );

          result.merge(ctx.validate()?);
        }
      }
    }

    Ok(true)
  }
}
