//! Privacy Validator
//!
//! Orchestrates the full validation pipeline for a transaction:
//! 1. Mode selection (explicit, locked)
//! 2. Proof generation (private mode)
//! 3. Envelope construction
//! 4. Policy evaluation
//! 5. Final sign/reject decision
//!
//! This is the top-level API that the CLI should call.

use coldstar_zk::types::{
    ProofBundle, SigningSummary, TransactionContext, TransactionMode, TransferEnvelope,
    VerificationResult,
};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;

use crate::mode::{ModeSelector, ModeState};
use crate::policy::{PolicyEvaluation, SigningPolicyEngine};

/// Final validation result — the single gate for signing.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationResult {
    /// Whether the transaction was approved for signing.
    pub approved: bool,
    /// Transaction mode.
    pub mode: TransactionMode,
    /// The constructed transfer envelope.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub envelope: Option<TransferEnvelope>,
    /// Proof bundle (private mode only).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub proof_bundle: Option<ProofBundle>,
    /// Policy evaluation results.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub policy_evaluation: Option<PolicyEvaluation>,
    /// Verification result from the ZK engine.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub verification_result: Option<VerificationResult>,
    /// Human-readable signing summary.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub signing_summary: Option<SigningSummary>,
    /// Reason for rejection (empty if approved).
    #[serde(default)]
    pub reason: String,
}

impl ValidationResult {
    /// Create a rejected result with the given reason.
    fn rejected(mode: TransactionMode, reason: impl Into<String>) -> Self {
        Self {
            approved: false,
            mode,
            envelope: None,
            proof_bundle: None,
            policy_evaluation: None,
            verification_result: None,
            signing_summary: None,
            reason: reason.into(),
        }
    }

    /// Returns a human-readable display string.
    pub fn display(&self) -> String {
        let status = if self.approved { "APPROVED" } else { "REJECTED" };
        let mode_str = self.mode.as_str();

        let mut lines = vec![
            "===========================================".to_string(),
            format!("  Transaction Validation: {:^14}  ", status),
            format!("  Mode: {:^31}  ", mode_str),
            "===========================================".to_string(),
        ];

        if let Some(ref summary) = self.signing_summary {
            lines.push(String::new());
            lines.push(format!("{}", summary));
        }

        if let Some(ref evaluation) = self.policy_evaluation {
            lines.push(String::new());
            lines.push(evaluation.display());
        }

        if !self.reason.is_empty() {
            lines.push(String::new());
            lines.push(format!("Reason: {}", self.reason));
        }

        lines.join("\n")
    }
}

/// Top-level validator that orchestrates the entire transaction
/// validation pipeline.
///
/// # Usage
///
/// ```no_run
/// use coldstar_privacy::PrivacyValidator;
///
/// let mut validator = PrivacyValidator::new(10_000_000_000, None, false);
/// validator.select_mode("private").unwrap();
///
/// // result = validator.validate_transaction(tx_context, Some("secret_key_hex"));
/// // if result.approved { /* proceed to signing */ }
/// ```
pub struct PrivacyValidator {
    mode_selector: ModeSelector,
    policy_engine: SigningPolicyEngine,
}

impl PrivacyValidator {
    /// Create a new privacy validator.
    ///
    /// # Arguments
    ///
    /// * `max_transfer_lamports` - Maximum transfer amount in lamports
    /// * `allowed_destinations` - Optional set of allowed recipient addresses
    /// * `require_destination_allowlist` - Whether to enforce the destination allowlist
    pub fn new(
        max_transfer_lamports: u64,
        allowed_destinations: Option<HashSet<String>>,
        require_destination_allowlist: bool,
    ) -> Self {
        Self {
            mode_selector: ModeSelector::new(),
            policy_engine: SigningPolicyEngine::new(
                max_transfer_lamports,
                allowed_destinations,
                require_destination_allowlist,
            ),
        }
    }

    // -- Mode selection ---------------------------------------------------

    /// Returns the currently selected transaction mode, if any.
    pub fn mode(&self) -> Option<TransactionMode> {
        self.mode_selector.mode()
    }

    /// Returns the current mode state.
    pub fn mode_state(&self) -> ModeState {
        self.mode_selector.state()
    }

    /// Select the transaction mode (`"public"` or `"private"`).
    ///
    /// # Errors
    ///
    /// Returns an error string if the mode is invalid or the state
    /// transition is forbidden.
    pub fn select_mode(&mut self, mode_str: &str) -> Result<(), String> {
        self.mode_selector
            .select(mode_str)
            .map(|_| ())
            .map_err(|e| e.to_string())
    }

    /// Reset the validator for a new transaction.
    pub fn reset(&mut self) {
        self.mode_selector.reset();
    }

    // -- Configuration pass-through --------------------------------------

    /// Update the maximum transfer limit.
    pub fn set_max_transfer(&mut self, lamports: u64) {
        self.policy_engine.set_max_transfer(lamports);
    }

    /// Add an address to the destination allowlist.
    pub fn add_allowed_destination(&mut self, address: &str) {
        self.policy_engine.add_allowed_destination(address);
    }

    // -- Full validation pipeline ----------------------------------------

    /// Run the complete validation pipeline.
    ///
    /// For **public mode**:
    /// - Build a public envelope
    /// - Run policy checks
    ///
    /// For **private mode**:
    /// - Verify proof bundle is present
    /// - Build a private envelope
    /// - Run full policy evaluation
    ///
    /// # Arguments
    ///
    /// * `tx_context` - The full transaction context
    /// * `proof_bundle` - Pre-generated proof bundle (required for private mode)
    ///
    /// # Returns
    ///
    /// [`ValidationResult`] with approval decision, envelope, and details.
    pub fn validate_transaction(
        &mut self,
        tx_context: &TransactionContext,
        proof_bundle: Option<ProofBundle>,
    ) -> ValidationResult {
        let Some(mode) = self.mode_selector.mode() else {
            return ValidationResult::rejected(
                TransactionMode::Public,
                "No mode selected. Call select_mode() first.",
            );
        };

        // Ensure mode matches context
        if tx_context.mode != mode {
            return ValidationResult::rejected(
                mode,
                format!(
                    "Mode mismatch: selector={}, context={}",
                    mode.as_str(),
                    tx_context.mode.as_str()
                ),
            );
        }

        match mode {
            TransactionMode::Public => self.validate_public(tx_context),
            TransactionMode::Private => {
                let Some(bundle) = proof_bundle else {
                    return ValidationResult::rejected(
                        TransactionMode::Private,
                        "Private mode requires a proof bundle",
                    );
                };
                self.validate_private(tx_context, bundle)
            }
        }
    }

    fn validate_public(&mut self, tx_context: &TransactionContext) -> ValidationResult {
        // Lock mode before signing
        if let Err(e) = self.mode_selector.lock() {
            return ValidationResult::rejected(TransactionMode::Public, format!("Lock failed: {}", e));
        }

        let envelope = build_public_envelope(tx_context);

        // Run policy evaluation
        let evaluation = self.policy_engine.evaluate(&envelope);

        let approved = evaluation.approved;
        let reason = if approved {
            String::new()
        } else {
            evaluation.reason.clone()
        };

        ValidationResult {
            approved,
            mode: TransactionMode::Public,
            envelope: Some(envelope),
            proof_bundle: None,
            policy_evaluation: Some(evaluation),
            verification_result: None,
            signing_summary: None,
            reason,
        }
    }

    fn validate_private(
        &mut self,
        tx_context: &TransactionContext,
        proof_bundle: ProofBundle,
    ) -> ValidationResult {
        // Lock mode after proof generation
        if let Err(e) = self.mode_selector.lock() {
            return ValidationResult::rejected(
                TransactionMode::Private,
                format!("Lock failed: {}", e),
            );
        }

        let envelope = build_private_envelope(tx_context, &proof_bundle);

        // Run policy evaluation
        let evaluation = self.policy_engine.evaluate(&envelope);

        let approved = evaluation.approved;
        let reason = if approved {
            String::new()
        } else {
            evaluation.reason.clone()
        };

        ValidationResult {
            approved,
            mode: TransactionMode::Private,
            envelope: Some(envelope),
            proof_bundle: Some(proof_bundle),
            policy_evaluation: Some(evaluation),
            verification_result: None,
            signing_summary: None,
            reason,
        }
    }

    // -- Verification (offline signer side) ------------------------------

    /// Verify a received envelope (called by offline signer).
    ///
    /// Runs policy evaluation against the envelope without requiring
    /// prior mode selection.
    pub fn verify_envelope(&mut self, envelope: &TransferEnvelope) -> ValidationResult {
        let evaluation = self.policy_engine.evaluate(envelope);

        let approved = evaluation.approved;
        let reason = if approved {
            String::new()
        } else {
            evaluation.reason.clone()
        };

        ValidationResult {
            approved,
            mode: envelope.mode,
            envelope: Some(envelope.clone()),
            proof_bundle: envelope.proof_bundle.clone(),
            policy_evaluation: Some(evaluation),
            verification_result: None,
            signing_summary: None,
            reason,
        }
    }
}

impl Default for PrivacyValidator {
    fn default() -> Self {
        Self::new(10_000_000_000, None, false)
    }
}

// -- Envelope construction helpers ---------------------------------------

/// Build a public-mode transfer envelope from a transaction context.
fn build_public_envelope(tx_context: &TransactionContext) -> TransferEnvelope {
    TransferEnvelope {
        version: "1.0.0".to_string(),
        mode: TransactionMode::Public,
        created_at: tx_context.created_at.clone(),
        transaction: tx_context.clone(),
        proof_bundle: None,
        integrity: String::new(),
    }
}

/// Build a private-mode transfer envelope with a proof bundle.
fn build_private_envelope(
    tx_context: &TransactionContext,
    proof_bundle: &ProofBundle,
) -> TransferEnvelope {
    TransferEnvelope {
        version: "1.0.0".to_string(),
        mode: TransactionMode::Private,
        created_at: tx_context.created_at.clone(),
        transaction: tx_context.clone(),
        proof_bundle: Some(proof_bundle.clone()),
        integrity: String::new(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use coldstar_zk::types::OwnershipProof;

    fn make_tx_context(mode: TransactionMode) -> TransactionContext {
        TransactionContext {
            unsigned_tx_b64: "AAAA".to_string(),
            from_pubkey: "SenderPubkey11111111111111111111111111111111".to_string(),
            to_pubkey: "RecipientPubkey1111111111111111111111111111".to_string(),
            amount_lamports: 1_000_000_000,
            fee_lamports: 5000,
            recent_blockhash: "BlockHash111111111111111111111111111111111".to_string(),
            mode,
            nonce: "unique_nonce_12345678901234567890".to_string(),
            created_at: "2026-01-01T00:00:00Z".to_string(),
        }
    }

    fn make_proof_bundle() -> ProofBundle {
        ProofBundle {
            ownership_proof: OwnershipProof {
                public_key: "aabb".to_string(),
                commitment_r: "ccdd".to_string(),
                challenge: "eeff".to_string(),
                response: "1122".to_string(),
                context_hash: "3344".to_string(),
            },
            range_proof: None,
            policy_proofs: vec![],
            binding: "deadbeefcafebabe".to_string(),
            nonce: "abcd1234".to_string(),
            version: "1.0.0".to_string(),
        }
    }

    #[test]
    fn no_mode_selected_rejects() {
        let mut validator = PrivacyValidator::default();
        let ctx = make_tx_context(TransactionMode::Public);
        let result = validator.validate_transaction(&ctx, None);
        assert!(!result.approved);
        assert!(result.reason.contains("No mode selected"));
    }

    #[test]
    fn public_mode_validates() {
        let mut validator = PrivacyValidator::default();
        validator.select_mode("public").unwrap();
        let ctx = make_tx_context(TransactionMode::Public);
        let result = validator.validate_transaction(&ctx, None);
        assert!(result.approved);
        assert!(result.envelope.is_some());
    }

    #[test]
    fn mode_mismatch_rejects() {
        let mut validator = PrivacyValidator::default();
        validator.select_mode("private").unwrap();
        let ctx = make_tx_context(TransactionMode::Public);
        let result = validator.validate_transaction(&ctx, None);
        assert!(!result.approved);
        assert!(result.reason.contains("Mode mismatch"));
    }

    #[test]
    fn private_without_bundle_rejects() {
        let mut validator = PrivacyValidator::default();
        validator.select_mode("private").unwrap();
        let ctx = make_tx_context(TransactionMode::Private);
        let result = validator.validate_transaction(&ctx, None);
        assert!(!result.approved);
        assert!(result.reason.contains("proof bundle"));
    }

    #[test]
    fn private_with_bundle_validates() {
        let mut validator = PrivacyValidator::default();
        validator.select_mode("private").unwrap();
        let ctx = make_tx_context(TransactionMode::Private);
        let bundle = make_proof_bundle();
        let result = validator.validate_transaction(&ctx, Some(bundle));
        assert!(result.approved);
        assert!(result.proof_bundle.is_some());
    }

    #[test]
    fn verify_envelope_works() {
        let mut validator = PrivacyValidator::default();
        let ctx = make_tx_context(TransactionMode::Public);
        let envelope = build_public_envelope(&ctx);
        let result = validator.verify_envelope(&envelope);
        assert!(result.approved);
    }

    #[test]
    fn display_shows_status() {
        let mut validator = PrivacyValidator::default();
        validator.select_mode("public").unwrap();
        let ctx = make_tx_context(TransactionMode::Public);
        let result = validator.validate_transaction(&ctx, None);
        let display = result.display();
        assert!(display.contains("APPROVED"));
        assert!(display.contains("public"));
    }

    #[test]
    fn reset_allows_new_transaction() {
        let mut validator = PrivacyValidator::default();
        validator.select_mode("public").unwrap();
        let ctx = make_tx_context(TransactionMode::Public);
        let _ = validator.validate_transaction(&ctx, None);

        validator.reset();
        assert!(validator.mode().is_none());

        validator.select_mode("private").unwrap();
        assert_eq!(validator.mode(), Some(TransactionMode::Private));
    }
}
