use std::{borrow::Cow, collections::HashMap, error::Error, fmt};

use regex::Regex;

use crate::parser::context;
use crate::request::response_capture::{
    parse_capture_operand, resolve_capture_value, CaptureSource, CaptureTarget, ResponseSnapshot,
};

#[derive(Debug, Clone)]
pub struct Assertion {
    pub raw: String,
    left: Operand,
    comparison: Comparison,
    message: Option<String>,
    guard: Option<Guard>,
}

#[derive(Debug, Clone)]
pub struct Guard {
    display: String,
    left: Operand,
    comparison: Comparison,
}

#[derive(Debug, Clone)]
enum Operand {
    Variable(String),
    Literal(String),
    Capture {
        source: CaptureSource,
        target: CaptureTarget,
        raw_path: String,
    },
}

#[derive(Debug, Clone)]
enum Comparison {
    Binary {
        operator: BinaryOperator,
        right: Operand,
    },
    Matches {
        pattern: MatchPattern,
    },
}

#[derive(Debug, Clone, Copy)]
enum BinaryOperator {
    Eq,
    Ne,
    Lt,
    Lte,
    Gt,
    Gte,
}

#[derive(Debug, Clone)]
enum MatchPattern {
    Regex(Regex),
    Contains(String),
}

#[derive(Debug)]
pub struct AssertionError(pub String);

impl fmt::Display for AssertionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl Error for AssertionError {}

impl Assertion {
    pub fn parse(raw: &str, context_map: &HashMap<String, String>) -> Result<Self, AssertionError> {
        let trimmed = raw.trim();
        let (guard_source, remainder) = extract_guard_prefix(trimmed)?;
        let remainder = remainder.trim_start();
        let without_prefix = remainder
            .strip_prefix('^')
            .ok_or_else(|| AssertionError("assertion must begin with '^'".into()))?
            .trim();

        if without_prefix.is_empty() {
            return Err(AssertionError("assertion expression is empty".into()));
        }

        let (expr_part, message_part) = split_message(without_prefix)?;

        let expr_injected = context::inject_from_variable(expr_part, context_map);
        let message = message_part.map(|m| {
            let injected = context::inject_from_variable(m, context_map);
            normalize_literal(&injected)
        });

        let (lhs, operator, rhs) = split_expression(expr_injected.as_str())?;

        let left = Operand::parse(lhs)?;
        let comparison = build_comparison(operator, rhs)?;

        let guard = match guard_source {
            Some(expr) => Some(Guard::parse(expr.as_str(), context_map)?),
            None => None,
        };

        Ok(Self {
            raw: trimmed.to_string(),
            left,
            comparison,
            message,
            guard,
        })
    }

    pub fn evaluate(
        &self,
        env: &HashMap<String, String>,
        current_snapshot: &ResponseSnapshot,
        dependency_snapshots: &HashMap<String, ResponseSnapshot>,
    ) -> Result<(), AssertionError> {
        let left_value = self
            .left
            .resolve(env, current_snapshot, dependency_snapshots)?;
        let passed =
            self.comparison
                .evaluate(&left_value, env, current_snapshot, dependency_snapshots)?;

        if passed {
            Ok(())
        } else {
            let failure_message = self.message.clone().unwrap_or_else(|| {
                format!("Assertion failed: {} (actual: '{}')", self.raw, left_value)
            });
            Err(AssertionError(failure_message))
        }
    }

    pub fn should_execute(
        &self,
        env: &HashMap<String, String>,
        current_snapshot: &ResponseSnapshot,
        dependency_snapshots: &HashMap<String, ResponseSnapshot>,
    ) -> Result<bool, AssertionError> {
        match &self.guard {
            Some(condition) => condition.evaluate(env, current_snapshot, dependency_snapshots),
            None => Ok(true),
        }
    }
}

impl Guard {
    pub fn parse(raw: &str, context_map: &HashMap<String, String>) -> Result<Self, AssertionError> {
        let trimmed = raw.trim();
        if trimmed.is_empty() {
            return Err(AssertionError("guard predicate is empty".into()));
        }

        let injected = context::inject_from_variable(trimmed, context_map);
        let (lhs, operator, rhs) = split_expression(injected.as_str())?;
        let normalized_rhs = normalize_guard_rhs(rhs);

        let left = Operand::parse(lhs)?;
        let comparison = build_comparison(operator, normalized_rhs.as_ref())?;

        Ok(Self {
            display: injected,
            left,
            comparison,
        })
    }

    pub fn evaluate(
        &self,
        env: &HashMap<String, String>,
        current_snapshot: &ResponseSnapshot,
        dependency_snapshots: &HashMap<String, ResponseSnapshot>,
    ) -> Result<bool, AssertionError> {
        let label = format!("[{}]", self.display);
        let left_value = self
            .left
            .resolve(env, current_snapshot, dependency_snapshots)
            .map_err(|err| {
                AssertionError(format!("Failed to evaluate guard {}: {}", label, err.0))
            })?;

        self.comparison
            .evaluate(&left_value, env, current_snapshot, dependency_snapshots)
            .map_err(|err| AssertionError(format!("Failed to evaluate guard {}: {}", label, err.0)))
    }

    pub fn evaluate_with_env(&self, env: &HashMap<String, String>) -> Result<bool, AssertionError> {
        let snapshot = ResponseSnapshot::default();
        let deps = HashMap::new();
        self.evaluate(env, &snapshot, &deps)
    }
}

fn extract_guard_prefix(input: &str) -> Result<(Option<String>, &str), AssertionError> {
    let trimmed = input.trim_start();
    if !trimmed.starts_with('[') {
        return Ok((None, input));
    }

    let bytes = trimmed.as_bytes();
    let mut idx = 1;
    let mut in_single = false;
    let mut in_double = false;

    while idx < bytes.len() {
        let ch = bytes[idx] as char;
        match ch {
            '\'' if !in_double => in_single = !in_single,
            '"' if !in_single => in_double = !in_double,
            ']' if !in_single && !in_double => {
                let predicate = trimmed[1..idx].trim();
                if predicate.is_empty() {
                    return Err(AssertionError("guard predicate is empty".into()));
                }
                let remainder = &trimmed[idx + 1..];
                return Ok((Some(predicate.to_string()), remainder));
            }
            _ => {}
        }
        idx += 1;
    }

    Err(AssertionError(
        "guard predicate is missing closing ']'".into(),
    ))
}

fn compare_numbers(left: &str, right: &str) -> Option<std::cmp::Ordering> {
    let lhs = left.parse::<f64>().ok()?;
    let rhs = right.parse::<f64>().ok()?;
    lhs.partial_cmp(&rhs)
}

fn build_comparison(operator: OperatorToken, rhs: &str) -> Result<Comparison, AssertionError> {
    match operator {
        OperatorToken::Matches => {
            let pattern = MatchPattern::parse(rhs)?;
            Ok(Comparison::Matches { pattern })
        }
        OperatorToken::Eq => Ok(Comparison::Binary {
            operator: BinaryOperator::Eq,
            right: Operand::parse(rhs)?,
        }),
        OperatorToken::Ne => Ok(Comparison::Binary {
            operator: BinaryOperator::Ne,
            right: Operand::parse(rhs)?,
        }),
        OperatorToken::Lt => Ok(Comparison::Binary {
            operator: BinaryOperator::Lt,
            right: Operand::parse(rhs)?,
        }),
        OperatorToken::Lte => Ok(Comparison::Binary {
            operator: BinaryOperator::Lte,
            right: Operand::parse(rhs)?,
        }),
        OperatorToken::Gt => Ok(Comparison::Binary {
            operator: BinaryOperator::Gt,
            right: Operand::parse(rhs)?,
        }),
        OperatorToken::Gte => Ok(Comparison::Binary {
            operator: BinaryOperator::Gte,
            right: Operand::parse(rhs)?,
        }),
    }
}

impl Comparison {
    fn evaluate(
        &self,
        left_value: &str,
        env: &HashMap<String, String>,
        current_snapshot: &ResponseSnapshot,
        dependency_snapshots: &HashMap<String, ResponseSnapshot>,
    ) -> Result<bool, AssertionError> {
        match self {
            Comparison::Binary { operator, right } => {
                let right_value = right.resolve(env, current_snapshot, dependency_snapshots)?;
                let right_ref = right_value.as_str();
                let outcome = match operator {
                    BinaryOperator::Eq => left_value == right_ref,
                    BinaryOperator::Ne => left_value != right_ref,
                    BinaryOperator::Lt => compare_numbers(left_value, right_ref)
                        .map(|ord| ord.is_lt())
                        .unwrap_or_else(|| left_value < right_ref),
                    BinaryOperator::Lte => compare_numbers(left_value, right_ref)
                        .map(|ord| !ord.is_gt())
                        .unwrap_or_else(|| left_value <= right_ref),
                    BinaryOperator::Gt => compare_numbers(left_value, right_ref)
                        .map(|ord| ord.is_gt())
                        .unwrap_or_else(|| left_value > right_ref),
                    BinaryOperator::Gte => compare_numbers(left_value, right_ref)
                        .map(|ord| !ord.is_lt())
                        .unwrap_or_else(|| left_value >= right_ref),
                };
                Ok(outcome)
            }
            Comparison::Matches { pattern } => {
                let outcome = match pattern {
                    MatchPattern::Regex(regex) => regex.is_match(left_value),
                    MatchPattern::Contains(expected) => left_value.contains(expected),
                };
                Ok(outcome)
            }
        }
    }
}

impl Operand {
    fn parse(input: &str) -> Result<Self, AssertionError> {
        let trimmed = input.trim();
        if trimmed.is_empty() {
            return Err(AssertionError("missing operand".into()));
        }

        if trimmed.starts_with('&') {
            let (source, target, raw_path) =
                parse_capture_operand(trimmed).map_err(|err| AssertionError(err.0))?;
            return Ok(Operand::Capture {
                source,
                target,
                raw_path,
            });
        }

        if trimmed.starts_with('$') {
            let key = trimmed.trim_start_matches('$').trim().to_string();
            if key.is_empty() {
                return Err(AssertionError("invalid variable reference".into()));
            }
            return Ok(Operand::Variable(key));
        }

        if is_numeric_literal(trimmed) {
            return Ok(Operand::Literal(trimmed.to_string()));
        }

        if is_boolean_literal(trimmed) {
            return Ok(Operand::Literal(trimmed.to_string()));
        }

        if (trimmed.starts_with('"') && trimmed.ends_with('"'))
            || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
        {
            return Ok(Operand::Literal(normalize_literal(trimmed)));
        }

        Ok(Operand::Variable(trimmed.to_string()))
    }

    fn resolve(
        &self,
        env: &HashMap<String, String>,
        current_snapshot: &ResponseSnapshot,
        dependency_snapshots: &HashMap<String, ResponseSnapshot>,
    ) -> Result<String, AssertionError> {
        match self {
            Operand::Variable(name) => env
                .get(name)
                .cloned()
                .ok_or_else(|| AssertionError(format!("No value found for variable '{}'.", name))),
            Operand::Literal(value) => Ok(value.clone()),
            Operand::Capture {
                source,
                target,
                raw_path,
            } => {
                let snapshot = match source {
                    CaptureSource::Current => current_snapshot,
                    CaptureSource::Dependency(dep) => dependency_snapshots.get(dep).ok_or_else(|| {
                        AssertionError(format!(
                            "No response snapshot available for dependency '{}' referenced in assertion.",
                            dep
                        ))
                    })?,
                };

                resolve_capture_value(target, raw_path, snapshot)
                    .map_err(|err| AssertionError(err.0))
            }
        }
    }
}

impl MatchPattern {
    fn parse(input: &str) -> Result<Self, AssertionError> {
        let trimmed = input.trim();
        if trimmed.is_empty() {
            return Err(AssertionError(
                "matches operator requires a right-hand operand".into(),
            ));
        }

        let literal = if (trimmed.starts_with('"') && trimmed.ends_with('"'))
            || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
        {
            normalize_literal(trimmed)
        } else {
            trimmed.to_string()
        };

        if literal.starts_with('/') && literal.ends_with('/') && literal.len() >= 2 {
            let pattern = &literal[1..literal.len() - 1];
            let regex = Regex::new(pattern)
                .map_err(|e| AssertionError(format!("Invalid regex in assertion: {}", e)))?;
            Ok(MatchPattern::Regex(regex))
        } else {
            Ok(MatchPattern::Contains(literal))
        }
    }
}

#[derive(Debug, Clone, Copy)]
enum OperatorToken {
    Eq,
    Ne,
    Lt,
    Lte,
    Gt,
    Gte,
    Matches,
}

fn split_expression(input: &str) -> Result<(&str, OperatorToken, &str), AssertionError> {
    let bytes = input.as_bytes();
    let mut in_single = false;
    let mut in_double = false;
    let mut i = 0;

    while i < bytes.len() {
        let ch = bytes[i] as char;
        match ch {
            '\'' if !in_double => in_single = !in_single,
            '"' if !in_single => in_double = !in_double,
            _ => {}
        }

        if in_single || in_double {
            i += 1;
            continue;
        }

        if i + 1 < bytes.len() {
            let two = &input[i..i + 2];
            let op = match two {
                "==" => Some(OperatorToken::Eq),
                "!=" => Some(OperatorToken::Ne),
                ">=" => Some(OperatorToken::Gte),
                "<=" => Some(OperatorToken::Lte),
                "~=" => Some(OperatorToken::Matches),
                _ => None,
            };
            if let Some(token) = op {
                let left = input[..i].trim();
                let right = input[i + 2..].trim();
                if left.is_empty() || right.is_empty() {
                    return Err(AssertionError("invalid assertion expression".into()));
                }
                return Ok((left, token, right));
            }
        }

        let op = match ch {
            '>' => Some(OperatorToken::Gt),
            '<' => Some(OperatorToken::Lt),
            _ => None,
        };

        if let Some(token) = op {
            let left = input[..i].trim();
            let right = input[i + 1..].trim();
            if left.is_empty() || right.is_empty() {
                return Err(AssertionError("invalid assertion expression".into()));
            }
            return Ok((left, token, right));
        }

        i += 1;
    }

    Err(AssertionError(
        "No valid operator found in assertion".into(),
    ))
}

fn split_message(input: &str) -> Result<(&str, Option<&str>), AssertionError> {
    let bytes = input.as_bytes();
    let mut in_single = false;
    let mut in_double = false;
    let mut i = 0;

    while i + 1 < bytes.len() {
        let ch = bytes[i] as char;
        match ch {
            '\'' if !in_double => in_single = !in_single,
            '"' if !in_single => in_double = !in_double,
            _ => {}
        }

        if !in_single && !in_double && ch == '|' && bytes[i + 1] as char == '|' {
            let expr = input[..i].trim();
            let message = input[i + 2..].trim();
            if expr.is_empty() {
                return Err(AssertionError("assertion expression is empty".into()));
            }
            if message.is_empty() {
                return Err(AssertionError("assertion message is empty".into()));
            }
            return Ok((expr, Some(message)));
        }

        i += 1;
    }

    Ok((input, None))
}

fn normalize_literal(input: &str) -> String {
    let trimmed = input.trim();
    if (trimmed.starts_with('"') && trimmed.ends_with('"'))
        || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
    {
        trimmed[1..trimmed.len() - 1].to_string()
    } else {
        trimmed.to_string()
    }
}

fn is_numeric_literal(input: &str) -> bool {
    let trimmed = input.trim();
    if trimmed.is_empty() {
        return false;
    }

    trimmed
        .chars()
        .all(|ch| matches!(ch, '0'..='9' | '.' | '-' | '+'))
}

fn is_boolean_literal(input: &str) -> bool {
    matches!(input.trim(), "true" | "false")
}

fn normalize_guard_rhs(input: &str) -> Cow<'_, str> {
    let trimmed = input.trim();
    if trimmed.is_empty()
        || trimmed.starts_with('$')
        || trimmed.starts_with('&')
        || trimmed.starts_with('"')
        || trimmed.starts_with('\'')
        || trimmed.starts_with('/')
        || is_numeric_literal(trimmed)
        || trimmed.chars().any(|ch| ch.is_ascii_uppercase())
    {
        Cow::Borrowed(trimmed)
    } else {
        Cow::Owned(format!("'{}'", trimmed))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use reqwest::header::HeaderMap;
    use reqwest::StatusCode;

    fn env(vars: &[(&str, &str)]) -> HashMap<String, String> {
        vars.iter()
            .map(|(k, v)| (k.to_string(), v.to_string()))
            .collect()
    }

    fn empty_snapshot() -> ResponseSnapshot {
        ResponseSnapshot {
            status: StatusCode::OK,
            headers: HeaderMap::new(),
            body: String::new(),
            json: None,
        }
    }

    fn deps() -> HashMap<String, ResponseSnapshot> {
        HashMap::new()
    }

    #[test]
    fn equality_assertion_passes() {
        let context = HashMap::new();
        let assertion = Assertion::parse("^ $FOO == 'bar'", &context).unwrap();
        let map = env(&[("FOO", "bar")]);
        let snapshot = empty_snapshot();
        let deps = deps();
        assert!(assertion.evaluate(&map, &snapshot, &deps).is_ok());
    }

    #[test]
    fn numeric_greater_than_passes() {
        let context = HashMap::new();
        let assertion = Assertion::parse("^ $COUNT > 10", &context).unwrap();
        let map = env(&[("COUNT", "12")]);
        let snapshot = empty_snapshot();
        let deps = deps();
        assert!(assertion.evaluate(&map, &snapshot, &deps).is_ok());
    }

    #[test]
    fn regex_matches() {
        let context = HashMap::new();
        let assertion = Assertion::parse("^ $VALUE ~= /foo.+/", &context).unwrap();
        let map = env(&[("VALUE", "foobarbaz")]);
        let snapshot = empty_snapshot();
        let deps = deps();
        assert!(assertion.evaluate(&map, &snapshot, &deps).is_ok());
    }

    #[test]
    fn contains_matches() {
        let context = HashMap::new();
        let assertion = Assertion::parse("^ $VALUE ~= 'bar'", &context).unwrap();
        let map = env(&[("VALUE", "foobarbaz")]);
        let snapshot = empty_snapshot();
        let deps = deps();
        assert!(assertion.evaluate(&map, &snapshot, &deps).is_ok());
    }

    #[test]
    fn assertion_failure_uses_default_message() {
        let context = HashMap::new();
        let assertion = Assertion::parse("^ $VALUE == 'expected'", &context).unwrap();
        let map = env(&[("VALUE", "actual")]);
        let snapshot = empty_snapshot();
        let deps = deps();
        let result = assertion.evaluate(&map, &snapshot, &deps);
        assert!(result.is_err());
        assert!(result.err().unwrap().0.contains("Assertion failed"));
    }

    #[test]
    fn assertion_failure_with_custom_message() {
        let context = HashMap::new();
        let assertion =
            Assertion::parse("^ $VALUE == 'expected' || 'custom failure'", &context).unwrap();
        let map = env(&[("VALUE", "actual")]);
        let snapshot = empty_snapshot();
        let deps = deps();
        let result = assertion.evaluate(&map, &snapshot, &deps);
        assert!(result.is_err());
        assert_eq!(result.err().unwrap().0, "custom failure");
    }

    #[test]
    fn capture_body_matches_regex() {
        let context = HashMap::new();
        let assertion = Assertion::parse("^ & body ~= /foo/", &context).unwrap();
        let map = HashMap::new();
        let snapshot = ResponseSnapshot {
            status: StatusCode::OK,
            headers: HeaderMap::new(),
            body: "foobarbaz".into(),
            json: None,
        };
        let deps = deps();
        assert!(assertion.evaluate(&map, &snapshot, &deps).is_ok());
    }

    #[test]
    fn capture_dependency_body_token() {
        let context = HashMap::new();
        let assertion = Assertion::parse("^ &[Login].body.token == 'secret'", &context).unwrap();
        let map = HashMap::new();
        let snapshot = empty_snapshot();
        let mut deps = HashMap::new();
        let dep_snapshot = ResponseSnapshot {
            status: StatusCode::OK,
            headers: HeaderMap::new(),
            body: "{\"token\":\"secret\"}".into(),
            json: serde_json::from_str("{\"token\":\"secret\"}").ok(),
        };
        deps.insert("Login".to_string(), dep_snapshot);
        assert!(assertion.evaluate(&map, &snapshot, &deps).is_ok());
    }

    #[test]
    fn capture_body_boolean_equals_boolean_literal() {
        let context = HashMap::new();
        let assertion = Assertion::parse("^ & body.boolean == true", &context).unwrap();
        let map = HashMap::new();
        let snapshot = ResponseSnapshot {
            status: StatusCode::OK,
            headers: HeaderMap::new(),
            body: "{\"boolean\":true}".into(),
            json: serde_json::from_str("{\"boolean\":true}").ok(),
        };
        let deps = deps();

        assert!(assertion.evaluate(&map, &snapshot, &deps).is_ok());
    }

    #[test]
    fn guarded_assertion_parses_guard() {
        let context = HashMap::new();
        let assertion = Assertion::parse("[ROLE == admin] ^ $STATUS == 200", &context).unwrap();
        assert!(assertion.guard.is_some());
    }

    #[test]
    fn guarded_assertion_skips_when_false() {
        let context = HashMap::new();
        let assertion = Assertion::parse("[ROLE == admin] ^ $STATUS == 200", &context).unwrap();
        let map = env(&[("ROLE", "user"), ("STATUS", "200")]);
        let snapshot = empty_snapshot();
        let deps = deps();
        let should_execute = assertion
            .should_execute(&map, &snapshot, &deps)
            .expect("guard evaluation should succeed");
        assert!(!should_execute);
    }

    #[test]
    fn guarded_assertion_executes_when_true() {
        let context = HashMap::new();
        let assertion = Assertion::parse("[ROLE == admin] ^ $STATUS == 200", &context).unwrap();
        let map = env(&[("ROLE", "admin"), ("STATUS", "200")]);
        let snapshot = empty_snapshot();
        let deps = deps();
        let should_execute = assertion
            .should_execute(&map, &snapshot, &deps)
            .expect("guard evaluation should succeed");
        assert!(should_execute);
        assert!(assertion.evaluate(&map, &snapshot, &deps).is_ok());
    }
}
