use crate::expect::ops::ExpectString;
use crate::expect_core::Context;
use crate::expect_core::ExpectOpError;
use crate::expect_core::ExpectOpResult;
use crate::internals::objects::StringObject;
use crate::JsonType;
use serde::Deserialize;
use serde::Serialize;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExpectStringSubOp {
    Empty,
    NotEmpty,
    Len(usize),
    MinLen(usize),
    MaxLen(usize),
    Contains(String),
    MatchesRegex(String),
}

impl ExpectStringSubOp {
    pub(crate) fn on_string(
        &self,
        parent: &ExpectString,
        context: &mut Context<'_>,
        received: &str,
    ) -> ExpectOpResult<()> {
        match self {
            Self::Empty => Self::on_string_empty(parent, context, received),
            Self::NotEmpty => Self::on_string_not_empty(parent, context, received),
            Self::Len(len) => Self::on_string_len(*len, parent, context, received),
            Self::MinLen(min_len) => Self::on_string_min_len(*min_len, parent, context, received),
            Self::MaxLen(max_len) => Self::on_string_max_len(*max_len, parent, context, received),
            Self::Contains(contains) => {
                Self::on_string_contains(contains, parent, context, received)
            }
            Self::MatchesRegex(pattern) => {
                Self::on_string_matches_regex(pattern, parent, context, received)
            }
        }
    }

    fn on_string_empty(
        parent: &ExpectString,
        context: &mut Context<'_>,
        received: &str,
    ) -> ExpectOpResult<()> {
        if !received.is_empty() {
            let error_message = format!(
                r#"expected empty string
    received {}"#,
                StringObject::from(received)
            );
            return Err(ExpectOpError::custom(parent, context, error_message));
        }

        Ok(())
    }

    fn on_string_not_empty(
        parent: &ExpectString,
        context: &mut Context<'_>,
        received: &str,
    ) -> ExpectOpResult<()> {
        if received.is_empty() {
            let error_message = format!(
                r#"expected non-empty string
    received {}"#,
                StringObject::from(received)
            );
            return Err(ExpectOpError::custom(parent, context, error_message));
        }

        Ok(())
    }

    fn on_string_len(
        len: usize,
        parent: &ExpectString,
        context: &mut Context<'_>,
        received: &str,
    ) -> ExpectOpResult<()> {
        if received.len() != len {
            let error_message = format!(
                r#"expected string to have {} characters, but it has {},
    received {}"#,
                len,
                received.len(),
                StringObject::from(received),
            );
            return Err(ExpectOpError::custom(parent, context, error_message));
        }

        Ok(())
    }

    fn on_string_min_len(
        min_len: usize,
        parent: &ExpectString,
        context: &mut Context<'_>,
        received: &str,
    ) -> ExpectOpResult<()> {
        if received.len() < min_len {
            let error_message = format!(
                r#"expected string to have at least {} characters, but it has {},
    received {}"#,
                min_len,
                received.len(),
                StringObject::from(received),
            );
            return Err(ExpectOpError::custom(parent, context, error_message));
        }

        Ok(())
    }

    fn on_string_max_len(
        max_len: usize,
        parent: &ExpectString,
        context: &mut Context<'_>,
        received: &str,
    ) -> ExpectOpResult<()> {
        if received.len() > max_len {
            let error_message = format!(
                r#"expected string to have at most {} characters, but it has {},
    received {}"#,
                max_len,
                received.len(),
                StringObject::from(received),
            );
            return Err(ExpectOpError::custom(parent, context, error_message));
        }

        Ok(())
    }

    fn on_string_contains(
        expected_sub_string: &str,
        _parent: &ExpectString,
        context: &mut Context<'_>,
        received: &str,
    ) -> ExpectOpResult<()> {
        if !received.contains(expected_sub_string) {
            return Err(ExpectOpError::ContainsNotFound {
                context: context.to_static(),
                json_type: JsonType::String,
                expected: StringObject::from(expected_sub_string).into(),
                received: StringObject::from(received).into(),
            });
        }

        Ok(())
    }

    fn on_string_matches_regex(
        pattern: &str,
        _parent: &ExpectString,
        context: &mut Context<'_>,
        received: &str,
    ) -> ExpectOpResult<()> {
        let regex = regex::Regex::new(pattern).map_err(|e| {
            ExpectOpError::custom(
                _parent,
                context,
                format!("invalid regex pattern '{}': {}", pattern, e),
            )
        })?;

        if !regex.is_match(received) {
            return Err(ExpectOpError::RegexNoMatch {
                context: context.to_static(),
                json_type: JsonType::String,
                pattern: pattern.to_string(),
                received: StringObject::from(received).into(),
            });
        }

        Ok(())
    }
}
