//! Pure Verification Module
//!
//! Provides pure verification logic using the ConscienceKernel without execution.
//! This is the core of Axiom's "verification-first" approach - you can verify
//! code properties without running anything.
//!
//! Key principle: verify() is pure, has no side effects, and is repeatable.

use crate::conscience::{ConscienceKernel, ConscienceQueryResult, EffectClass, QueryCategory};
use crate::error::{AxiomError, AxiomResult, ErrorKind};
use crate::parser::ast::{BinOp, Expr, UnaryOp};
use crate::policy::Policy;
use std::collections::HashMap;

/// Result of a verification operation
#[derive(Debug, Clone)]
pub struct Verdict {
    /// Whether the intent is allowed by the policy
    pub allowed: bool,
    /// Optional reason for denial (None if allowed)
    pub reason: Option<String>,
    /// Category of the policy decision
    pub category: QueryCategory,
    /// Human-readable guidance about the decision
    pub guidance: String,
}

impl Verdict {
    /// Returns true if the intent is allowed
    pub fn allowed(&self) -> bool {
        self.allowed
    }

    /// Returns the reason for denial, or None if allowed
    pub fn reason(&self) -> Option<&str> {
        self.reason.as_deref()
    }

    /// Returns guidance about the decision
    pub fn guidance(&self) -> &str {
        &self.guidance
    }

    /// Returns the query category
    pub fn category(&self) -> &QueryCategory {
        &self.category
    }
}

/// Pure verifier - checks intents against policy without executing anything
pub struct Verifier {
    /// The conscience kernel for evaluation
    conscience: ConscienceKernel,
    /// Intent declarations from the policy
    intents: HashMap<String, crate::parser::ast::IntentDecl>,
}

impl Verifier {
    /// Create a new verifier from a policy
    ///
    /// # Example
    /// ```no_run
    /// use axiom_lang::policy::PolicyLoader;
    /// use axiom_lang::verification::Verifier;
    ///
    /// let policy = PolicyLoader::load_file("security.axm")?;
    /// let verifier = Verifier::new(policy);
    /// # Ok::<(), axiom_lang::error::AxiomError>(())
    /// ```
    pub fn new(policy: Policy) -> Self {
        Verifier {
            conscience: ConscienceKernel::new(),
            intents: policy.intents,
        }
    }

    /// Pure verification - check if an intent would be allowed
    ///
    /// This method is pure (no side effects), repeatable (same inputs = same output),
    /// and does not execute any code. It only evaluates the conscience kernel's
    /// policy rules.
    ///
    /// # Arguments
    /// * `intent_name` - The name of the intent to verify
    /// * `fields` - Key-value pairs representing the intent's parameters
    ///
    /// # Example
    /// ```no_run
    /// use axiom_lang::policy::PolicyLoader;
    /// use axiom_lang::verification::Verifier;
    ///
    /// let policy = PolicyLoader::load_file("security.axm")?;
    /// let verifier = Verifier::new(policy);
    ///
    /// let verdict = verifier.verify("WriteFile", &[
    ///     ("path", "/tmp/data.txt"),
    ///     ("content", "hello world"),
    /// ])?;
    ///
    /// if verdict.allowed() {
    ///     println!("Policy allows this write");
    /// } else {
    ///     println!("Policy denied: {}", verdict.reason().unwrap());
    /// }
    /// # Ok::<(), axiom_lang::error::AxiomError>(())
    /// ```
    pub fn verify(&self, intent_name: &str, fields: &[(&str, &str)]) -> AxiomResult<Verdict> {
        let intent = self.intents.get(intent_name).ok_or_else(|| AxiomError {
            kind: ErrorKind::UndefinedFunction,
            message: format!("Intent '{}' not found in policy", intent_name),
            span: None,
        })?;

        let effect = intent
            .clauses
            .effect
            .as_ref()
            .and_then(|e| EffectClass::from_str(e))
            .unwrap_or(EffectClass::Noop);

        let mut field_map = HashMap::new();
        for (key, value) in fields {
            field_map.insert(key.to_string(), value.to_string());
        }

        // Evaluate pre-conditions first
        for pre in &intent.clauses.pre {
            match self.eval_precondition(pre, &field_map) {
                Ok(true) => {}
                Ok(false) => {
                    return Ok(Verdict {
                        allowed: false,
                        reason: Some(format!("Pre-condition failed for intent '{}'", intent_name)),
                        category: QueryCategory::ConscienceCore,
                        guidance: "One or more pre-conditions were not satisfied".to_string(),
                    });
                }
                Err(e) => {
                    return Ok(Verdict {
                        allowed: false,
                        reason: Some(format!("Pre-condition evaluation error: {}", e.message)),
                        category: QueryCategory::ConscienceCore,
                        guidance: "Pre-condition could not be evaluated".to_string(),
                    });
                }
            }
        }

        let result: ConscienceQueryResult = self.conscience.query(intent_name, &effect, &field_map);

        let verdict = Verdict {
            allowed: result.permitted,
            reason: result.deny_reason.clone(),
            category: result.category,
            guidance: result.guidance,
        };

        Ok(verdict)
    }

    /// Evaluate a pre-condition expression against field values
    fn eval_precondition(
        &self,
        expr: &Expr,
        fields: &HashMap<String, String>,
    ) -> Result<bool, AxiomError> {
        match expr {
            Expr::BoolLiteral(b, _) => Ok(*b),

            Expr::Ident(name, span) => {
                // Look up the field value
                fields
                    .get(name)
                    .map(|v| {
                        if v == "true" {
                            true
                        } else if v == "false" {
                            false
                        } else {
                            true
                        }
                    })
                    .ok_or_else(|| AxiomError {
                        kind: ErrorKind::UndefinedVariable,
                        message: format!("Unknown field '{}' in pre-condition", name),
                        span: Some(span.clone()),
                    })
            }

            Expr::Unary(UnaryOp::Not, inner, span) => {
                let inner_val = self.eval_precondition(inner, fields)?;
                Ok(!inner_val)
            }

            Expr::Binary(left, op, right, span) => match op {
                BinOp::And => {
                    let l = self.eval_precondition(left, fields)?;
                    let r = self.eval_precondition(right, fields)?;
                    Ok(l && r)
                }
                BinOp::Or => {
                    let l = self.eval_precondition(left, fields)?;
                    let r = self.eval_precondition(right, fields)?;
                    Ok(l || r)
                }
                BinOp::Eq | BinOp::NotEq => {
                    let l_str = self.eval_to_string(left, fields)?;
                    let r_str = self.eval_to_string(right, fields)?;
                    let eq = l_str == r_str;
                    Ok(if matches!(op, BinOp::Eq) { eq } else { !eq })
                }
                BinOp::Lt | BinOp::Gt | BinOp::LtEq | BinOp::GtEq => {
                    let l_num = self.eval_to_number(left, fields)?;
                    let r_num = self.eval_to_number(right, fields)?;
                    let result = match op {
                        BinOp::Lt => l_num < r_num,
                        BinOp::Gt => l_num > r_num,
                        BinOp::LtEq => l_num <= r_num,
                        BinOp::GtEq => l_num >= r_num,
                        _ => unreachable!(),
                    };
                    Ok(result)
                }
                _ => Ok(true),
            },

            Expr::Call(name, args, span) => self.eval_builtin_call(name, args, fields, span),

            _ => Ok(true),
        }
    }

    /// Evaluate an expression to a string representation
    fn eval_to_string(
        &self,
        expr: &Expr,
        fields: &HashMap<String, String>,
    ) -> Result<String, AxiomError> {
        match expr {
            Expr::StringLiteral(s, _) => Ok(s.clone()),
            Expr::Ident(name, span) => fields.get(name).cloned().ok_or_else(|| AxiomError {
                kind: ErrorKind::UndefinedVariable,
                message: format!("Unknown field '{}' in pre-condition", name),
                span: Some(span.clone()),
            }),
            Expr::BoolLiteral(b, _) => Ok(b.to_string()),
            Expr::IntLiteral(n, _) => Ok(n.to_string()),
            Expr::FloatLiteral(n, _) => Ok(n.to_string()),
            _ => Ok(format!("{:?}", expr)),
        }
    }

    /// Evaluate an expression to a number for comparisons
    fn eval_to_number(
        &self,
        expr: &Expr,
        fields: &HashMap<String, String>,
    ) -> Result<i64, AxiomError> {
        match expr {
            Expr::IntLiteral(n, _) => Ok(*n),
            Expr::FloatLiteral(n, _) => Ok(*n as i64),
            Expr::Ident(name, span) => {
                let s = fields.get(name).ok_or_else(|| AxiomError {
                    kind: ErrorKind::UndefinedVariable,
                    message: format!("Unknown field '{}' in pre-condition", name),
                    span: Some(span.clone()),
                })?;
                s.parse::<i64>().map_err(|_| AxiomError {
                    kind: ErrorKind::TypeMismatch,
                    message: format!("Cannot convert '{}' to number", s),
                    span: Some(span.clone()),
                })
            }
            Expr::Call(name, args, span) if name == "length" => {
                if args.len() != 1 {
                    return Err(AxiomError {
                        kind: ErrorKind::ArgumentCount,
                        message: "length() requires exactly 1 argument".to_string(),
                        span: Some(span.clone()),
                    });
                }
                let s = self.eval_to_string(&args[0], fields)?;
                Ok(s.len() as i64)
            }
            _ => Err(AxiomError {
                kind: ErrorKind::TypeMismatch,
                message: format!("Cannot evaluate {:?} to number", expr),
                span: None,
            }),
        }
    }

    /// Evaluate a builtin function call in a pre-condition
    fn eval_builtin_call(
        &self,
        name: &str,
        args: &[Expr],
        fields: &HashMap<String, String>,
        span: &crate::lexer::token::Span,
    ) -> Result<bool, AxiomError> {
        match name {
            "contains" => {
                if args.len() != 2 {
                    return Err(AxiomError {
                        kind: ErrorKind::ArgumentCount,
                        message: "contains() requires exactly 2 arguments".to_string(),
                        span: Some(span.clone()),
                    });
                }
                let haystack = self.eval_to_string(&args[0], fields)?;
                let needle = self.eval_to_string(&args[1], fields)?;
                Ok(haystack.contains(&needle))
            }
            "starts_with" => {
                if args.len() != 2 {
                    return Err(AxiomError {
                        kind: ErrorKind::ArgumentCount,
                        message: "starts_with() requires exactly 2 arguments".to_string(),
                        span: Some(span.clone()),
                    });
                }
                let s = self.eval_to_string(&args[0], fields)?;
                let prefix = self.eval_to_string(&args[1], fields)?;
                Ok(s.starts_with(&prefix))
            }
            "ends_with" => {
                if args.len() != 2 {
                    return Err(AxiomError {
                        kind: ErrorKind::ArgumentCount,
                        message: "ends_with() requires exactly 2 arguments".to_string(),
                        span: Some(span.clone()),
                    });
                }
                let s = self.eval_to_string(&args[0], fields)?;
                let suffix = self.eval_to_string(&args[1], fields)?;
                Ok(s.ends_with(&suffix))
            }
            "matches" => {
                if args.len() != 2 {
                    return Err(AxiomError {
                        kind: ErrorKind::ArgumentCount,
                        message: "matches() requires exactly 2 arguments".to_string(),
                        span: Some(span.clone()),
                    });
                }
                let s = self.eval_to_string(&args[0], fields)?;
                let pattern = self.eval_to_string(&args[1], fields)?;
                let re = regex::Regex::new(&pattern).map_err(|e| AxiomError {
                    kind: ErrorKind::InvalidRegex,
                    message: format!("Invalid regex pattern '{}': {}", pattern, e),
                    span: Some(span.clone()),
                })?;
                Ok(re.is_match(&s))
            }
            "length" => {
                if args.len() != 1 {
                    return Err(AxiomError {
                        kind: ErrorKind::ArgumentCount,
                        message: "length() requires exactly 1 argument".to_string(),
                        span: Some(span.clone()),
                    });
                }
                let s = self.eval_to_string(&args[0], fields)?;
                Ok(s.len() > 0)
            }
            _ => Err(AxiomError {
                kind: ErrorKind::UndefinedFunction,
                message: format!("Unknown function '{}' in pre-condition", name),
                span: Some(span.clone()),
            }),
        }
    }

    /// Get a list of all intent names in the policy
    pub fn intent_names(&self) -> Vec<&str> {
        self.intents.keys().map(|s| s.as_str()).collect()
    }

    /// Check if an intent exists in the policy
    pub fn has_intent(&self, name: &str) -> bool {
        self.intents.contains_key(name)
    }

    /// Get the effect class for an intent
    pub fn intent_effect(&self, name: &str) -> Option<EffectClass> {
        self.intents
            .get(name)
            .and_then(|i| i.clauses.effect.as_ref())
            .and_then(|e| EffectClass::from_str(e))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::policy::PolicyLoader;

    #[test]
    fn test_verify_safe_read() {
        let source = r#"
            module test {
                intent ReadFile {
                    takes: path: String;
                    gives: content: String;
                    effect: READ;
                    conscience: path_safety;
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Safe path should be allowed (baseline_allow covers READ)
        let verdict = verifier
            .verify("ReadFile", &[("path", "/tmp/data.txt")])
            .unwrap();
        assert!(verdict.allowed());
    }

    #[test]
    fn test_verify_dangerous_path_denied() {
        let source = r#"
            module test {
                intent ReadFile {
                    takes: path: String;
                    gives: content: String;
                    effect: READ;
                    conscience: path_safety;
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Dangerous path should be denied by path_safety predicate
        let verdict = verifier
            .verify("ReadFile", &[("path", "/etc/shadow")])
            .unwrap();
        assert!(!verdict.allowed());
        assert!(verdict.reason().is_some());
    }

    #[test]
    fn test_verify_write_with_conscience_predicates() {
        let source = r#"
            module test {
                intent WriteFile {
                    takes: path: String, content: String;
                    gives: success: bool;
                    effect: WRITE;
                    conscience: path_safety, no_exfiltrate;
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Write to /tmp should pass both path_safety and no_exfiltrate
        let verdict = verifier
            .verify(
                "WriteFile",
                &[("path", "/tmp/test.txt"), ("content", "hello")],
            )
            .unwrap();
        // Note: This passes because /tmp is not in the denied paths list
        // and is not a network path (no_exfiltrate passes)
        assert!(verdict.allowed());

        // Write to dangerous path should be denied
        let verdict = verifier
            .verify(
                "WriteFile",
                &[("path", "/etc/passwd"), ("content", "hello")],
            )
            .unwrap();
        assert!(!verdict.allowed());
    }

    #[test]
    fn test_verify_network_exfiltration() {
        let source = r#"
            module test {
                intent SendData {
                    takes: url: String, data: String;
                    gives: success: bool;
                    effect: NETWORK;
                    conscience: no_exfiltrate;
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Undeclared channel should be denied by no_exfiltrate
        let verdict = verifier
            .verify(
                "SendData",
                &[("url", "http://evil.com"), ("data", "secrets")],
            )
            .unwrap();
        assert!(!verdict.allowed());
    }

    #[test]
    fn test_verify_unknown_intent() {
        let source = r#"
            module test {
                intent KnownIntent {
                    takes: x: i64;
                    gives: y: i64;
                    effect: NOOP;
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Unknown intent should return an error
        let result = verifier.verify("UnknownIntent", &[("x", "42")]);
        assert!(result.is_err());
    }

    #[test]
    fn test_verify_is_pure() {
        // Verification should be repeatable - same inputs = same outputs
        let source = r#"
            module test {
                intent ReadFile {
                    takes: path: String;
                    gives: content: String;
                    effect: READ;
                    conscience: path_safety;
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        let fields = [("path", "/etc/shadow")];

        // Call verify multiple times with same inputs
        let verdict1 = verifier.verify("ReadFile", &fields).unwrap();
        let verdict2 = verifier.verify("ReadFile", &fields).unwrap();
        let verdict3 = verifier.verify("ReadFile", &fields).unwrap();

        // All results should be identical
        assert_eq!(verdict1.allowed(), verdict2.allowed());
        assert_eq!(verdict2.allowed(), verdict3.allowed());
        assert_eq!(verdict1.reason(), verdict2.reason());
        assert_eq!(verdict2.reason(), verdict3.reason());
    }

    #[test]
    fn test_verifier_introspection() {
        let source = r#"
            module test {
                intent A { takes: x: i64; gives: y: i64; effect: NOOP; }
                intent B { takes: x: i64; gives: y: i64; effect: READ; }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        assert!(verifier.has_intent("A"));
        assert!(verifier.has_intent("B"));
        assert!(!verifier.has_intent("C"));

        assert_eq!(verifier.intent_effect("A"), Some(EffectClass::Noop));
        assert_eq!(verifier.intent_effect("B"), Some(EffectClass::Read));

        let mut names = verifier.intent_names();
        names.sort();
        assert_eq!(names, vec!["A", "B"]);
    }

    #[test]
    fn test_precondition_contains() {
        let source = r#"
            module test {
                intent RunCommand {
                    takes: command: String;
                    gives: output: String;
                    effect: NOOP;
                    pre: !contains(command, "rm -rf /");
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Should be denied - contains "rm -rf /"
        let verdict = verifier
            .verify("RunCommand", &[("command", "rm -rf /")])
            .unwrap();
        assert!(!verdict.allowed());
        assert!(verdict.reason().unwrap().contains("Pre-condition"));

        // Should be allowed - doesn't contain "rm -rf /"
        let verdict = verifier
            .verify("RunCommand", &[("command", "ls -la")])
            .unwrap();
        assert!(verdict.allowed());
    }

    #[test]
    fn test_precondition_fork_bomb() {
        let source = r#"
            module test {
                intent RunCommand {
                    takes: command: String;
                    gives: output: String;
                    effect: NOOP;
                    pre: !contains(command, ":(){ :|:& };:");
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Should be denied - is a fork bomb
        let verdict = verifier
            .verify("RunCommand", &[("command", ":(){ :|:& };:")])
            .unwrap();
        assert!(!verdict.allowed());

        // Should be allowed - not a fork bomb
        let verdict = verifier
            .verify("RunCommand", &[("command", "echo hello")])
            .unwrap();
        assert!(verdict.allowed());
    }

    #[test]
    fn test_precondition_starts_with() {
        let source = r#"
            module test {
                intent ReadFile {
                    takes: path: String;
                    gives: content: String;
                    effect: READ;
                    pre: starts_with(path, "/tmp/");
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Should be allowed - starts with /tmp/
        let verdict = verifier
            .verify("ReadFile", &[("path", "/tmp/data.txt")])
            .unwrap();
        assert!(verdict.allowed());

        // Should be denied - doesn't start with /tmp/
        let verdict = verifier
            .verify("ReadFile", &[("path", "/etc/passwd")])
            .unwrap();
        assert!(!verdict.allowed());
    }

    #[test]
    fn test_precondition_ends_with() {
        let source = r#"
            module test {
                intent VerifyPolicy {
                    takes: policy_path: String;
                    gives: verdict: String;
                    effect: READ;
                    pre: ends_with(policy_path, ".axm");
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Should be allowed - ends with .axm
        let verdict = verifier
            .verify("VerifyPolicy", &[("policy_path", "/axiom/policy.axm")])
            .unwrap();
        assert!(verdict.allowed());

        // Should be denied - doesn't end with .axm
        let verdict = verifier
            .verify("VerifyPolicy", &[("policy_path", "/axiom/policy.txt")])
            .unwrap();
        assert!(!verdict.allowed());
    }

    #[test]
    fn test_precondition_multiple() {
        let source = r#"
            module test {
                intent RunCommand {
                    takes: command: String;
                    gives: output: String;
                    effect: NOOP;
                    pre: !contains(command, "rm -rf /");
                    pre: !contains(command, "dd if=/dev/zero");
                    pre: !starts_with(command, "shutdown");
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Should be denied - contains "rm -rf /"
        let verdict = verifier
            .verify("RunCommand", &[("command", "rm -rf /")])
            .unwrap();
        assert!(!verdict.allowed());

        // Should be denied - contains "dd if=/dev/zero"
        let verdict = verifier
            .verify("RunCommand", &[("command", "dd if=/dev/zero")])
            .unwrap();
        assert!(!verdict.allowed());

        // Should be denied - starts with "shutdown"
        let verdict = verifier
            .verify("RunCommand", &[("command", "shutdown -h now")])
            .unwrap();
        assert!(!verdict.allowed());

        // Should be allowed - safe command
        let verdict = verifier
            .verify("RunCommand", &[("command", "ls -la")])
            .unwrap();
        assert!(verdict.allowed());
    }

    #[test]
    fn test_precondition_with_conscience() {
        let source = r#"
            module test {
                intent RunCommand {
                    takes: command: String, verified: String;
                    gives: output: String;
                    effect: EXECUTE;
                    conscience: no_bypass_verification;
                    pre: !contains(command, "rm -rf /");
                }
            }
        "#;

        let policy = PolicyLoader::load_source(source).unwrap();
        let verifier = Verifier::new(policy);

        // Should be denied by conscience - not verified
        let verdict = verifier
            .verify(
                "RunCommand",
                &[("command", "ls -la"), ("verified", "false")],
            )
            .unwrap();
        assert!(!verdict.allowed());

        // Should be denied by pre-condition - contains rm -rf /
        let verdict = verifier
            .verify(
                "RunCommand",
                &[("command", "rm -rf /"), ("verified", "true")],
            )
            .unwrap();
        assert!(!verdict.allowed());
        assert!(verdict.reason().unwrap().contains("Pre-condition"));

        // Should be allowed - verified and safe command
        let verdict = verifier
            .verify("RunCommand", &[("command", "ls -la"), ("verified", "true")])
            .unwrap();
        assert!(verdict.allowed());
    }
}
