use crate::agent::Agent;
use crate::agent::JACS_VERSION_DATE_FIELDNAME;
use crate::agent::JACS_VERSION_FIELDNAME;
use crate::agent::boilerplate::BoilerPlate;
use crate::agent::document::{DocumentTraits, JACSDocument};
use crate::agent::loaders::FileLoader;
use crate::agent::{
    AGENT_AGREEMENT_FIELDNAME, DOCUMENT_AGENT_SIGNATURE_FIELDNAME,
    DOCUMENT_AGREEMENT_HASH_FIELDNAME, JACS_PREVIOUS_VERSION_FIELDNAME, SHA256_FIELDNAME,
};

use crate::crypt::hash::hash_public_key;
use crate::crypt::hash::hash_string;
use crate::error::JacsError;
use crate::schema::utils::ValueExt;
use crate::validation::normalize_agent_id;
use chrono::Utc;
use serde_json::Value;
use serde_json::json;
use std::collections::HashSet;
use tracing::{debug, info, warn};

// WARNING: This module uses `.expect()` in 14 signing/verification call sites.
// These will panic on malformed input. Agreements are not in the shipped launch
// path — callers are gated behind the `agreements` feature flag — so the panics
// are not reachable in production builds.
//
// Before enabling the `agreements` feature by default or promoting this module
// to a user-facing API, convert every `.expect()` to `?` error propagation.

/// Options for creating and checking agreements.
///
/// All fields are optional. When omitted, the existing behavior is preserved:
/// - No timeout (agreement never expires)
/// - No quorum (all listed agents must sign)
/// - No algorithm constraints (any algorithm accepted)
#[derive(Debug, Clone, Default)]
pub struct AgreementOptions {
    /// ISO 8601 deadline after which the agreement expires.
    pub timeout: Option<String>,
    /// Minimum number of signatures required (M-of-N). If `None`, all agents must sign.
    pub quorum: Option<u32>,
    /// Only accept signatures using one of these algorithms.
    pub required_algorithms: Option<Vec<String>>,
    /// Minimum strength tier: "classical" or "post-quantum".
    pub minimum_strength: Option<String>,
}

/// Returns the strength tier for a signing algorithm.
/// "post-quantum" algorithms: pq2025
/// Everything else is "classical".
pub fn algorithm_strength(algo: &str) -> &'static str {
    match algo {
        "pq2025" => "post-quantum",
        _ => "classical",
    }
}

/// Returns true if `algo` meets the `minimum_strength` requirement.
fn meets_strength_requirement(algo: &str, minimum_strength: &str) -> bool {
    match minimum_strength {
        "post-quantum" => algorithm_strength(algo) == "post-quantum",
        // "classical" accepts everything
        _ => true,
    }
}

pub trait Agreement {
    /// given a document id and a list of agents, return an updated document with an agreement field
    /// fails if an agreement field exists
    /// no other fields can be modified or the agreement fails
    /// overwrites previous agreements
    fn create_agreement(
        &mut self,
        document_key: &str,
        agentids: &[String],
        question: Option<&str>,
        context: Option<&str>,
        agreement_fieldname: Option<String>,
    ) -> Result<JACSDocument, JacsError>;
    /// Create an agreement with extended options (timeout, quorum, algorithm constraints).
    fn create_agreement_with_options(
        &mut self,
        document_key: &str,
        agentids: &[String],
        question: Option<&str>,
        context: Option<&str>,
        agreement_fieldname: Option<String>,
        options: &AgreementOptions,
    ) -> Result<JACSDocument, JacsError>;
    /// given a document id and a list of agents, return an updated document
    fn add_agents_to_agreement(
        &mut self,
        document_key: &str,
        agentids: &[String],
        agreement_fieldname: Option<String>,
    ) -> Result<JACSDocument, JacsError>;
    /// given a document id and a list of agents, return an updated document
    fn remove_agents_from_agreement(
        &mut self,
        document_key: &str,
        agentids: &[String],
        agreement_fieldname: Option<String>,
    ) -> Result<JACSDocument, JacsError>;
    /// given a document id sign a document, return an updated document
    fn sign_agreement(
        &mut self,
        document_key: &str,
        agreement_fieldname: Option<String>,
    ) -> Result<JACSDocument, JacsError>;
    /// given a document, check all agreement signatures
    fn check_agreement(
        &self,
        document_key: &str,
        agreement_fieldname: Option<String>,
    ) -> Result<String, JacsError>;

    /* HYGIENE-004: Potentially dead code - verify tests pass before removal
    /// given a document, check all agreement signatures
    fn has_agreement(&self, document_key: &str) -> Result<bool, JacsError>;
    */

    /// agreements update documents
    /// however this updates the document, which updates, version, lastversion and version date
    /// the agreement itself needs it's own hash to track
    /// on a subset of fields
    /// the standard hash detects any changes at all
    fn agreement_hash(&self, value: Value, agreement_fieldname: &str) -> Result<String, JacsError>;

    /// remove fields that should not be used for agreement signature
    fn trim_fields_for_hashing_and_signing(
        &self,
        value: Value,
        agreement_fieldname: &str,
    ) -> Result<(String, Vec<String>), JacsError>;

    fn agreement_get_question_and_context(
        &self,
        document_key: &str,
        agreement_fieldname: Option<String>,
    ) -> Result<(String, String), JacsError>;
}

impl Agreement for Agent {
    fn agreement_hash(&self, value: Value, agreement_fieldname: &str) -> Result<String, JacsError> {
        let (values_as_string, _fields) =
            self.trim_fields_for_hashing_and_signing(value, agreement_fieldname)?;
        Ok(hash_string(&values_as_string))
    }

    /* HYGIENE-004: Potentially dead code - verify tests pass before removal
    /// ineffienct because it doesn't pull from the document
    fn has_agreement(&self, document_key: &str) -> Result<bool, JacsError> {
        let document = self.get_document(document_key)?;
        let agreement_fieldname_key = AGENT_AGREEMENT_FIELDNAME.to_string();
        let agreement_field = document.value.get(&agreement_fieldname_key);
        if agreement_field.is_some() {
            return Ok(true);
        }
        Ok(false)
    }
    */
    // ignore these extra fields will change
    fn trim_fields_for_hashing_and_signing(
        &self,
        value: Value,
        agreement_fieldname: &str,
    ) -> Result<(String, Vec<String>), JacsError> {
        let mut new_obj: Value = value.clone();
        new_obj.as_object_mut().map(|obj| {
            obj.remove(DOCUMENT_AGREEMENT_HASH_FIELDNAME);
            obj.remove(JACS_PREVIOUS_VERSION_FIELDNAME);
            obj.remove(JACS_VERSION_FIELDNAME);
            obj.remove(JACS_VERSION_DATE_FIELDNAME)
        });
        // update_document materializes jacsFiles as [] for documents without attachments.
        // Normalize missing and empty attachment lists so agreement hashes are stable.
        if new_obj.get("jacsFiles").is_none()
            && let Some(obj) = new_obj.as_object_mut()
        {
            obj.insert("jacsFiles".to_string(), json!([]));
        }

        let (values_as_string, fields) =
            Agent::get_values_as_string(&new_obj, None, agreement_fieldname)?;
        Ok((values_as_string, fields))
    }

    fn create_agreement(
        &mut self,
        document_key: &str,
        agentids: &[String],
        question: Option<&str>,
        context: Option<&str>,
        agreement_fieldname: Option<String>,
    ) -> Result<JACSDocument, JacsError> {
        self.create_agreement_with_options(
            document_key,
            agentids,
            question,
            context,
            agreement_fieldname,
            &AgreementOptions::default(),
        )
    }

    fn create_agreement_with_options(
        &mut self,
        document_key: &str,
        agentids: &[String],
        question: Option<&str>,
        context: Option<&str>,
        agreement_fieldname: Option<String>,
        options: &AgreementOptions,
    ) -> Result<JACSDocument, JacsError> {
        let agreement_fieldname_key = match agreement_fieldname {
            Some(key) => key,
            _ => AGENT_AGREEMENT_FIELDNAME.to_string(),
        };

        // Validate quorum
        if let Some(q) = options.quorum {
            if q == 0 {
                return Err(JacsError::DocumentError(
                    "Quorum must be at least 1".to_string(),
                ));
            }
            if q as usize > agentids.len() {
                return Err(JacsError::DocumentError(format!(
                    "Quorum ({}) cannot exceed the number of agents ({})",
                    q,
                    agentids.len()
                )));
            }
        }

        // Validate timeout is in the future
        if let Some(ref timeout_str) = options.timeout {
            let deadline = chrono::DateTime::parse_from_rfc3339(timeout_str).map_err(|e| {
                JacsError::DocumentError(format!(
                    "Invalid timeout '{}': must be ISO 8601 date-time (e.g., '2025-12-31T23:59:59Z'). Error: {}",
                    timeout_str, e
                ))
            })?;
            if deadline <= Utc::now() {
                return Err(JacsError::DocumentError(format!(
                    "Timeout '{}' is in the past",
                    timeout_str
                )));
            }
        }

        // Validate minimum_strength
        if let Some(ref strength) = options.minimum_strength {
            if strength != "classical" && strength != "post-quantum" {
                return Err(JacsError::DocumentError(format!(
                    "Invalid minimumStrength '{}': must be 'classical' or 'post-quantum'",
                    strength
                )));
            }
        }

        let document = self.get_document(document_key)?;
        let mut value = document.value;

        let context_string = context.unwrap_or_default();
        let question_string = question.unwrap_or_default();

        let agreement_hash_value =
            json!(self.agreement_hash(value.clone(), &agreement_fieldname_key)?);
        value[DOCUMENT_AGREEMENT_HASH_FIELDNAME] = agreement_hash_value.clone();

        let mut agreement_obj = json!({
            "signatures": [],
            "agentIDs": agentids,
            "question": question_string,
            "context": context_string
        });

        // Add optional fields from AgreementOptions
        if let Some(ref timeout) = options.timeout {
            agreement_obj["timeout"] = json!(timeout);
        }
        if let Some(quorum) = options.quorum {
            agreement_obj["quorum"] = json!(quorum);
        }
        if let Some(ref algos) = options.required_algorithms {
            agreement_obj["requiredAlgorithms"] = json!(algos);
        }
        if let Some(ref strength) = options.minimum_strength {
            agreement_obj["minimumStrength"] = json!(strength);
        }

        value[agreement_fieldname_key.clone()] = agreement_obj;

        let updated_document =
            self.update_document(document_key, &serde_json::to_string(&value)?, None, None)?;

        let agreement_hash_value_after =
            json!(self.agreement_hash(updated_document.value.clone(), &agreement_fieldname_key)?);
        // could be unit test, but want this in for safety
        if agreement_hash_value != agreement_hash_value_after {
            return Err(JacsError::DocumentError(format!(
                "Agreement field hashes don't match for document_key {}",
                document_key
            )));
        }

        if value[SHA256_FIELDNAME] == updated_document.value[SHA256_FIELDNAME] {
            return Err(JacsError::DocumentError(format!(
                "document hashes should have changed {}",
                document_key
            )));
        };

        let quorum_display = options
            .quorum
            .map(|q| q.to_string())
            .unwrap_or_else(|| "all".to_string());
        info!(
            event = "agreement_created",
            document_id = %document_key,
            agent_count = agentids.len(),
            quorum = %quorum_display,
            has_timeout = options.timeout.is_some(),
            "Agreement created"
        );

        Ok(updated_document)
    }

    /// TODO also remove their signature
    fn remove_agents_from_agreement(
        &mut self,
        document_key: &str,
        agentids: &[String],
        agreement_fieldname: Option<String>,
    ) -> Result<JACSDocument, JacsError> {
        let agreement_fieldname_key = match agreement_fieldname {
            Some(key) => key,
            _ => AGENT_AGREEMENT_FIELDNAME.to_string(),
        };
        let document = self.get_document(document_key)?;
        let mut value = document.value;
        let _ = value[DOCUMENT_AGREEMENT_HASH_FIELDNAME].clone();

        if let Some(jacs_agreement) = value.get_mut(agreement_fieldname_key) {
            if let Some(agents) = jacs_agreement.get_mut("agentIDs") {
                if let Some(agents_array) = agents.as_array_mut() {
                    let agents_vec: Vec<String> = agents_array
                        .iter()
                        .map(|v| v.as_str().unwrap().to_string())
                        .collect();
                    let merged_agents = subtract_vecs(&agents_vec, agentids);
                    *agents = json!(merged_agents);
                } else {
                    return Err(
                        "Agreement modification failed: no agents present in agreement".into(),
                    );
                }
            } else {
                return Err("Agreement modification failed: agents field not found".into());
            }
        } else {
            return Err("Agreement modification failed: no agreement field present".into());
        }

        let updated_document =
            self.update_document(document_key, &serde_json::to_string(&value)?, None, None)?;

        Ok(updated_document)
    }

    fn add_agents_to_agreement(
        &mut self,
        document_key: &str,
        agentids: &[String],
        agreement_fieldname: Option<String>,
    ) -> Result<JACSDocument, JacsError> {
        let agreement_fieldname_key = match agreement_fieldname {
            Some(key) => key,
            _ => AGENT_AGREEMENT_FIELDNAME.to_string(),
        };
        let document = self.get_document(document_key)?;
        let mut value = document.value;
        let _ = value[DOCUMENT_AGREEMENT_HASH_FIELDNAME].clone();

        // Normalize agent IDs - ensure we're only using the base ID without version
        let normalized_agent_ids: Vec<String> = agentids
            .iter()
            .map(|id| {
                // If the ID contains a colon (indicating ID:version format), just take the part before the colon
                if let Some(pos) = id.find(':') {
                    id[0..pos].to_string()
                } else {
                    id.clone()
                }
            })
            .collect();

        if let Some(jacs_agreement) = value.get_mut(agreement_fieldname_key.clone()) {
            if let Some(agents) = jacs_agreement.get_mut("agentIDs") {
                if let Some(agents_array) = agents.as_array_mut() {
                    // Normalize existing agent IDs in the same way
                    let existing_agents: Vec<String> = agents_array
                        .iter()
                        .map(|v| {
                            let id_str = v.as_str().unwrap().to_string();
                            if let Some(pos) = id_str.find(':') {
                                id_str[0..pos].to_string()
                            } else {
                                id_str
                            }
                        })
                        .collect();

                    let merged_agents =
                        merge_without_duplicates(&existing_agents, &normalized_agent_ids);
                    *agents = json!(merged_agents);
                } else {
                    *agents = json!(normalized_agent_ids);
                }
            } else {
                jacs_agreement["agentIDs"] = json!(normalized_agent_ids);
            }
        } else {
            value[agreement_fieldname_key] = json!({
                "agentIDs": normalized_agent_ids,
                "signatures": [],
            });
        }

        let updated_document =
            self.update_document(document_key, &serde_json::to_string(&value)?, None, None)?;

        Ok(updated_document)
    }

    // TODO Check if signing agent is already in agent_ids
    // if not ???
    fn sign_agreement(
        &mut self,
        document_key: &str,
        agreement_fieldname: Option<String>,
    ) -> Result<JACSDocument, JacsError> {
        let agreement_fieldname_key = match agreement_fieldname {
            Some(ref key) => key.to_string(),
            _ => AGENT_AGREEMENT_FIELDNAME.to_string(),
        };

        let document = self.get_document(document_key)?;
        let mut value = document.value;

        // --- Pre-signing checks for timeout and algorithm constraints ---
        if let Some(jacs_agreement) = value.get(&agreement_fieldname_key) {
            // Timeout check
            if let Some(timeout_str) = jacs_agreement.get("timeout").and_then(|v| v.as_str()) {
                if let Ok(deadline) = chrono::DateTime::parse_from_rfc3339(timeout_str) {
                    if Utc::now() > deadline {
                        warn!(
                            event = "agreement_expired",
                            document_id = %document_key,
                            deadline = %timeout_str,
                            "Cannot sign expired agreement"
                        );
                        return Err(JacsError::DocumentError(format!(
                            "Cannot sign: agreement has expired (deadline was {})",
                            timeout_str
                        )));
                    }
                }
            }

            // Algorithm constraint checks against this agent's signing algorithm
            if let Some(algo) = &self.key_algorithm {
                if let Some(required) = jacs_agreement
                    .get("requiredAlgorithms")
                    .and_then(|v| v.as_array())
                {
                    let required_algos: Vec<String> = required
                        .iter()
                        .filter_map(|v| v.as_str().map(String::from))
                        .collect();
                    if !required_algos.contains(algo) {
                        return Err(JacsError::DocumentError(format!(
                            "Cannot sign: agent's algorithm '{}' is not in requiredAlgorithms {:?}",
                            algo, required_algos
                        )));
                    }
                }
                if let Some(strength) = jacs_agreement
                    .get("minimumStrength")
                    .and_then(|v| v.as_str())
                {
                    if !meets_strength_requirement(algo, strength) {
                        return Err(JacsError::DocumentError(format!(
                            "Cannot sign: agent's algorithm '{}' does not meet minimumStrength '{}'",
                            algo, strength
                        )));
                    }
                }
            }
        }
        let binding = value[DOCUMENT_AGREEMENT_HASH_FIELDNAME].clone();
        let original_agreement_hash_value = binding.as_str();
        // todo use this
        let _calculated_agreement_hash_value =
            self.agreement_hash(value.clone(), &agreement_fieldname_key)?;
        let signing_agent_id = self.get_id().expect("agent id");
        //  generate signature object
        let (_values_as_string, fields) =
            self.trim_fields_for_hashing_and_signing(value.clone(), &agreement_fieldname_key)?;
        let agents_signature: Value = self.signing_procedure(
            &value.clone(),
            Some(&fields),
            &agreement_fieldname_key.to_string(),
        )?;

        // Normalize signing agent ID to avoid duplicates - extract just the ID part
        let normalized_agent_id = normalize_agent_id(&signing_agent_id).to_string();

        // Check if agent ID (normalized) is already in the agreement
        let mut agent_already_in_agreement = false;
        if let Some(jacs_agreement) = value.get(agreement_fieldname_key.clone())
            && let Some(agents) = jacs_agreement.get("agentIDs")
            && let Some(agents_array) = agents.as_array()
        {
            for agent in agents_array {
                let agent_str = agent.as_str().unwrap_or("");
                if normalize_agent_id(agent_str) == normalized_agent_id {
                    agent_already_in_agreement = true;
                    break;
                }
            }
        }

        // Keep agent list normalized without requiring an ownership-changing update.
        if !agent_already_in_agreement {
            if let Some(jacs_agreement) = value.get_mut(&agreement_fieldname_key) {
                if let Some(agent_ids) = jacs_agreement.get_mut("agentIDs") {
                    if let Some(agent_ids_array) = agent_ids.as_array_mut() {
                        agent_ids_array.push(json!(normalized_agent_id.clone()));
                    } else {
                        *agent_ids = json!([normalized_agent_id.clone()]);
                    }
                } else {
                    jacs_agreement["agentIDs"] = json!([normalized_agent_id.clone()]);
                }
            } else {
                value[agreement_fieldname_key.clone()] = json!({
                    "agentIDs": [normalized_agent_id.clone()],
                    "signatures": []
                });
            }
        }

        debug!(
            "agents_signature {}",
            serde_json::to_string_pretty(&agents_signature).expect("agents_signature print")
        );

        if let Some(jacs_agreement) = value.get_mut(&agreement_fieldname_key) {
            if let Some(signatures) = jacs_agreement.get_mut("signatures") {
                if let Some(signatures_array) = signatures.as_array_mut() {
                    signatures_array.push(agents_signature);
                } else {
                    *signatures = json!([agents_signature]);
                }
            } else {
                jacs_agreement["signatures"] = json!([agents_signature]);
            }
        } else {
            value[agreement_fieldname_key.clone()] = json!({
                "agentIDs": [normalized_agent_id],
                "signatures": [agents_signature]
            });
        }

        let pre_update_hash = value[SHA256_FIELDNAME].clone();

        // Agreement signatures are collaborative: any listed signer may append a signature.
        // We version and re-sign here instead of calling update_document(), which enforces
        // single-owner edits for generic document updates.
        let new_version = uuid::Uuid::new_v4().to_string();
        let last_version = value[JACS_VERSION_FIELDNAME].clone();
        value[JACS_PREVIOUS_VERSION_FIELDNAME] = last_version;
        value[JACS_VERSION_FIELDNAME] = json!(new_version);
        value[JACS_VERSION_DATE_FIELDNAME] = json!(crate::time_utils::now_rfc3339());
        value[DOCUMENT_AGENT_SIGNATURE_FIELDNAME] =
            self.signing_procedure(&value, None, DOCUMENT_AGENT_SIGNATURE_FIELDNAME)?;
        let document_hash = self.hash_doc(&value)?;
        value[SHA256_FIELDNAME] = json!(document_hash);

        let updated_document = self.store_jacs_document(&value)?;

        let agreement_hash_value_after =
            self.agreement_hash(updated_document.value.clone(), &agreement_fieldname_key)?;

        // could be unit test, but want this in for safety
        if original_agreement_hash_value != Some(&agreement_hash_value_after) {
            return Err(JacsError::DocumentError(format!(
                "aborting signature on agreement. field hashes don't match for document_key {} \n {} {}",
                document_key,
                original_agreement_hash_value.expect("original_agreement_hash_value"),
                agreement_hash_value_after
            )));
        }

        if pre_update_hash == updated_document.value[SHA256_FIELDNAME] {
            return Err(JacsError::DocumentError(format!(
                "document hashes should have changed {}",
                document_key
            )));
        };

        // Compute signature counts for structured logging
        let sig_count = updated_document
            .value
            .get(&agreement_fieldname_key)
            .and_then(|a| a.get("signatures"))
            .and_then(|s| s.as_array())
            .map(|a| a.len())
            .unwrap_or(0);
        let total_agents = updated_document
            .value
            .get(&agreement_fieldname_key)
            .and_then(|a| a.get("agentIDs"))
            .and_then(|a| a.as_array())
            .map(|a| a.len())
            .unwrap_or(0);
        let quorum = updated_document
            .value
            .get(&agreement_fieldname_key)
            .and_then(|a| a.get("quorum"))
            .and_then(|v| v.as_u64())
            .map(|v| v as usize);
        let required = quorum.unwrap_or(total_agents);

        info!(
            event = "signature_added",
            document_id = %document_key,
            signer_id = %normalized_agent_id,
            current = sig_count,
            total = total_agents,
            required = required,
            "Agreement signature added"
        );

        if sig_count >= required {
            info!(
                event = "quorum_reached",
                document_id = %document_key,
                signatures = sig_count,
                required = required,
                total = total_agents,
                "Agreement quorum reached"
            );
        }

        Ok(updated_document)
    }

    /// get human readable fields
    fn agreement_get_question_and_context(
        &self,
        document_key: &str,
        agreement_fieldname: Option<String>,
    ) -> Result<(String, String), JacsError> {
        let agreement_fieldname_key = match agreement_fieldname {
            Some(key) => key,
            _ => AGENT_AGREEMENT_FIELDNAME.to_string(),
        };

        let document = self.get_document(document_key)?;
        let error_message = format!("{} missing", DOCUMENT_AGREEMENT_HASH_FIELDNAME);
        let original_agreement_hash_value = document.value[DOCUMENT_AGREEMENT_HASH_FIELDNAME]
            .as_str()
            .expect(&error_message);
        let calculated_agreement_hash_value =
            self.agreement_hash(document.value.clone(), &agreement_fieldname_key)?;
        if original_agreement_hash_value != calculated_agreement_hash_value {
            return Err("Agreement verification failed: agreement hashes do not match".into());
        }

        if let Some(jacs_agreement) = document.value.get(agreement_fieldname_key) {
            let question = jacs_agreement
                .get_str("question")
                .expect("agreement_get_question_and_context question field");
            let context = jacs_agreement
                .get_str("context")
                .expect("agreement_get_question_and_context question field");

            return Ok((question.to_string(), context.to_string()));
        }
        Err("Agreement verification failed: document has no agreement".into())
    }

    /// checking agreements requires you have the public key of each signatory
    /// if the document hashes don't match or there are unsigned, it will fail
    ///
    /// Enforces optional agreement constraints:
    /// - **timeout**: if set and expired, returns an error
    /// - **quorum**: if set, only `quorum` signatures are required (M-of-N)
    /// - **requiredAlgorithms**: if set, rejects signatures not using a listed algorithm
    /// - **minimumStrength**: if set, rejects signatures below the strength tier
    fn check_agreement(
        &self,
        document_key: &str,
        agreement_fieldname: Option<String>,
    ) -> Result<String, JacsError> {
        let agreement_fieldname_key: String = match agreement_fieldname {
            Some(ref key) => key.to_string(),
            _ => AGENT_AGREEMENT_FIELDNAME.to_string(),
        };

        let document = self.get_document(document_key)?;
        let local_doc_value = document.value.clone();
        let error_message = format!("{} missing", DOCUMENT_AGREEMENT_HASH_FIELDNAME);
        let original_agreement_hash_value = document.value[DOCUMENT_AGREEMENT_HASH_FIELDNAME]
            .as_str()
            .expect(&error_message);
        let calculated_agreement_hash_value =
            self.agreement_hash(document.value.clone(), &agreement_fieldname_key)?;
        if original_agreement_hash_value != calculated_agreement_hash_value {
            return Err("Agreement verification failed: agreement hashes do not match".into());
        }

        let jacs_agreement = document
            .value
            .get(agreement_fieldname_key.clone())
            .ok_or("Agreement verification failed: document has no agreement")?;

        // --- Timeout check ---
        if let Some(timeout_str) = jacs_agreement.get("timeout").and_then(|v| v.as_str()) {
            if let Ok(deadline) = chrono::DateTime::parse_from_rfc3339(timeout_str) {
                if Utc::now() > deadline {
                    warn!(
                        event = "agreement_expired",
                        document_id = %document_key,
                        deadline = %timeout_str,
                        "Agreement has expired"
                    );
                    return Err(JacsError::DocumentError(format!(
                        "Agreement has expired: deadline was {}",
                        timeout_str
                    )));
                }
            }
        }

        // --- Read quorum ---
        let quorum: Option<u32> = jacs_agreement
            .get("quorum")
            .and_then(|v| v.as_u64())
            .map(|v| v as u32);

        // --- Read algorithm constraints ---
        let required_algorithms: Option<Vec<String>> = jacs_agreement
            .get("requiredAlgorithms")
            .and_then(|v| v.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|v| v.as_str().map(String::from))
                    .collect()
            });
        let minimum_strength: Option<&str> = jacs_agreement
            .get("minimumStrength")
            .and_then(|v| v.as_str());

        // --- Unsigned check (respects quorum) ---
        let unsigned = document.agreement_unsigned_agents(agreement_fieldname.clone())?;
        let all_agents = document.agreement_requested_agents(agreement_fieldname.clone())?;
        let signed_count = all_agents.len() - unsigned.len();

        if let Some(q) = quorum {
            if (signed_count as u32) < q {
                return Err(JacsError::DocumentError(format!(
                    "Quorum not met: need {} signatures, have {} (unsigned: {:?})",
                    q, signed_count, unsigned
                )));
            }
        } else {
            // Original behavior: all agents must sign
            if !unsigned.is_empty() {
                return Err(JacsError::DocumentError(format!(
                    "not all agents have signed: {:?} {:?}",
                    unsigned, jacs_agreement
                )));
            }
        }

        // --- Verify each signature ---
        if let Some(signatures) = jacs_agreement.get("signatures")
            && let Some(signatures_array) = signatures.as_array()
        {
            for signature in signatures_array {
                let agent_id_and_version = format!(
                    "{}:{}",
                    signature
                        .get_str("agentID")
                        .expect("REASON agreement signature agentID"),
                    signature
                        .get_str("agentVersion")
                        .expect("REASON agreement signature agentVersion")
                )
                .to_string();

                let noted_hash = signature
                    .get_str("publicKeyHash")
                    .expect("REASON noted_hash")
                    .to_string();

                let public_key_enc_type = signature
                    .get_str("signingAlgorithm")
                    .expect("REASON public_key_enc_type")
                    .to_string();

                // --- Algorithm constraint enforcement ---
                if let Some(ref algos) = required_algorithms {
                    if !algos.contains(&public_key_enc_type) {
                        return Err(JacsError::DocumentError(format!(
                            "Signature from {} uses algorithm '{}' which is not in requiredAlgorithms {:?}",
                            agent_id_and_version, public_key_enc_type, algos
                        )));
                    }
                }
                if let Some(strength) = minimum_strength {
                    if !meets_strength_requirement(&public_key_enc_type, strength) {
                        return Err(JacsError::DocumentError(format!(
                            "Signature from {} uses algorithm '{}' which does not meet minimumStrength '{}'",
                            agent_id_and_version, public_key_enc_type, strength
                        )));
                    }
                }

                let agents_signature = signature
                    .get_str("signature")
                    .expect("REASON public_key_enc_type")
                    .to_string();
                let agents_public_key = self.fs_load_public_key(&noted_hash)?;
                let new_hash = hash_public_key(&agents_public_key);
                if new_hash != noted_hash {
                    return Err(JacsError::CryptoError(format!(
                        "wrong public key for {} , {}",
                        agent_id_and_version, noted_hash
                    )));
                }
                debug!(
                    "testing agreement sig agent_id_and_version {} {} {} ",
                    agent_id_and_version, noted_hash, public_key_enc_type
                );
                let (_values_as_string, fields) = self.trim_fields_for_hashing_and_signing(
                    local_doc_value.clone(),
                    &agreement_fieldname_key,
                )?;
                // Verify against the individual agreement signature object so
                // temporal metadata (iat/jti) is read from the signer entry,
                // not from the parent agreement container.
                let mut signature_context = document.value.clone();
                signature_context[agreement_fieldname_key.clone()] = signature.clone();
                self.signature_verification_procedure(
                    &signature_context,
                    Some(&fields),
                    &agreement_fieldname_key.to_string(),
                    agents_public_key,
                    Some(public_key_enc_type.clone()),
                    Some(noted_hash.clone()),
                    Some(agents_signature),
                )?;
            }
            if let Some(q) = quorum {
                return Ok(format!(
                    "Quorum met: {}/{} signatures verified (required: {})",
                    signed_count,
                    all_agents.len(),
                    q
                ));
            }
            return Ok("All signatures passed".to_string());
        }
        Err("Agreement verification failed: document has no agreement".into())
    }
}

pub fn merge_without_duplicates(vec1: &[String], vec2: &[String]) -> Vec<String> {
    let mut set: HashSet<String> = HashSet::new();

    for item in vec1 {
        set.insert(item.to_string());
    }
    for item in vec2 {
        set.insert(item.to_string());
    }
    set.into_iter().collect()
}

pub fn subtract_vecs(vec1: &[String], vec2: &[String]) -> Vec<String> {
    debug!("subtract_vecs A {:?} {:?} ", vec1, vec2);

    let to_remove: HashSet<&String> = vec2.iter().collect();
    let return_vec1 = vec1
        .iter()
        .filter(|item| !to_remove.contains(item))
        .cloned()
        .collect();
    debug!("subtract_vecs B {:?}- {:?} = {:?}", vec1, vec2, return_vec1);
    return_vec1
}
