use polars::prelude::*;
use pyo3::prelude::*;

use super::datatype::PyDataTypeExpr;
use crate::PyExpr;
use crate::conversion::Wrap;
use crate::error::PyPolarsErr;

#[pymethods]
impl PyExpr {
    fn str_join(&self, delimiter: &str, ignore_nulls: bool) -> Self {
        self.inner
            .clone()
            .str()
            .join(delimiter, ignore_nulls)
            .into()
    }

    #[pyo3(signature = (format, strict, exact, cache))]
    fn str_to_date(&self, format: Option<String>, strict: bool, exact: bool, cache: bool) -> Self {
        let format = format.map(|x| x.into());

        let options = StrptimeOptions {
            format,
            strict,
            exact,
            cache,
        };
        self.inner.clone().str().to_date(options).into()
    }

    #[pyo3(signature = (format, time_unit, time_zone, strict, exact, cache, ambiguous))]
    fn str_to_datetime(
        &self,
        format: Option<String>,
        time_unit: Option<Wrap<TimeUnit>>,
        time_zone: Wrap<Option<TimeZone>>,
        strict: bool,
        exact: bool,
        cache: bool,
        ambiguous: Self,
    ) -> Self {
        let format = format.map(|x| x.into());
        let time_zone = time_zone.0;

        let options = StrptimeOptions {
            format,
            strict,
            exact,
            cache,
        };
        self.inner
            .clone()
            .str()
            .to_datetime(
                time_unit.map(|tu| tu.0),
                time_zone,
                options,
                ambiguous.inner,
            )
            .into()
    }

    #[pyo3(signature = (format, strict, cache))]
    fn str_to_time(&self, format: Option<String>, strict: bool, cache: bool) -> Self {
        let format = format.map(|x| x.into());

        let options = StrptimeOptions {
            format,
            strict,
            cache,
            exact: true,
        };
        self.inner.clone().str().to_time(options).into()
    }

    fn str_strip_chars(&self, matches: Self) -> Self {
        self.inner.clone().str().strip_chars(matches.inner).into()
    }

    fn str_strip_chars_start(&self, matches: Self) -> Self {
        self.inner
            .clone()
            .str()
            .strip_chars_start(matches.inner)
            .into()
    }

    fn str_strip_chars_end(&self, matches: Self) -> Self {
        self.inner
            .clone()
            .str()
            .strip_chars_end(matches.inner)
            .into()
    }

    fn str_strip_prefix(&self, prefix: Self) -> Self {
        self.inner.clone().str().strip_prefix(prefix.inner).into()
    }

    fn str_strip_suffix(&self, suffix: Self) -> Self {
        self.inner.clone().str().strip_suffix(suffix.inner).into()
    }

    fn str_slice(&self, offset: Self, length: Self) -> Self {
        self.inner
            .clone()
            .str()
            .slice(offset.inner, length.inner)
            .into()
    }

    fn str_head(&self, n: Self) -> Self {
        self.inner.clone().str().head(n.inner).into()
    }

    fn str_tail(&self, n: Self) -> Self {
        self.inner.clone().str().tail(n.inner).into()
    }

    fn str_to_uppercase(&self) -> Self {
        self.inner.clone().str().to_uppercase().into()
    }

    fn str_to_lowercase(&self) -> Self {
        self.inner.clone().str().to_lowercase().into()
    }

    #[cfg(feature = "nightly")]
    fn str_to_titlecase(&self) -> Self {
        self.inner.clone().str().to_titlecase().into()
    }

    fn str_len_bytes(&self) -> Self {
        self.inner.clone().str().len_bytes().into()
    }

    fn str_len_chars(&self) -> Self {
        self.inner.clone().str().len_chars().into()
    }

    #[cfg(feature = "regex")]
    fn str_replace_n(&self, pat: Self, val: Self, literal: bool, n: i64) -> Self {
        self.inner
            .clone()
            .str()
            .replace_n(pat.inner, val.inner, literal, n)
            .into()
    }

    #[cfg(feature = "regex")]
    fn str_replace_all(&self, pat: Self, val: Self, literal: bool) -> Self {
        self.inner
            .clone()
            .str()
            .replace_all(pat.inner, val.inner, literal)
            .into()
    }

    fn str_normalize(&self, form: Wrap<UnicodeForm>) -> Self {
        self.inner.clone().str().normalize(form.0).into()
    }

    fn str_reverse(&self) -> Self {
        self.inner.clone().str().reverse().into()
    }

    fn str_pad_start(&self, length: PyExpr, fill_char: char) -> Self {
        self.inner
            .clone()
            .str()
            .pad_start(length.inner, fill_char)
            .into()
    }

    fn str_pad_end(&self, length: PyExpr, fill_char: char) -> Self {
        self.inner
            .clone()
            .str()
            .pad_end(length.inner, fill_char)
            .into()
    }

    fn str_zfill(&self, length: PyExpr) -> Self {
        self.inner.clone().str().zfill(length.inner).into()
    }

    #[pyo3(signature = (pat, literal, strict))]
    #[cfg(feature = "regex")]
    fn str_contains(&self, pat: Self, literal: Option<bool>, strict: bool) -> Self {
        match literal {
            Some(true) => self.inner.clone().str().contains_literal(pat.inner).into(),
            _ => self.inner.clone().str().contains(pat.inner, strict).into(),
        }
    }

    #[pyo3(signature = (pat, literal, strict))]
    #[cfg(feature = "regex")]
    fn str_find(&self, pat: Self, literal: Option<bool>, strict: bool) -> Self {
        match literal {
            Some(true) => self.inner.clone().str().find_literal(pat.inner).into(),
            _ => self.inner.clone().str().find(pat.inner, strict).into(),
        }
    }

    fn str_ends_with(&self, sub: Self) -> Self {
        self.inner.clone().str().ends_with(sub.inner).into()
    }

    fn str_starts_with(&self, sub: Self) -> Self {
        self.inner.clone().str().starts_with(sub.inner).into()
    }

    fn str_hex_encode(&self) -> Self {
        self.inner.clone().str().hex_encode().into()
    }

    #[cfg(feature = "binary_encoding")]
    fn str_hex_decode(&self, strict: bool) -> Self {
        self.inner.clone().str().hex_decode(strict).into()
    }

    fn str_base64_encode(&self) -> Self {
        self.inner.clone().str().base64_encode().into()
    }

    #[cfg(feature = "binary_encoding")]
    fn str_base64_decode(&self, strict: bool) -> Self {
        self.inner.clone().str().base64_decode(strict).into()
    }

    #[pyo3(signature = (base, dtype=Some(Wrap(DataType::Int64)), strict=true))]
    fn str_to_integer(&self, base: Self, dtype: Option<Wrap<DataType>>, strict: bool) -> Self {
        self.inner
            .clone()
            .str()
            .to_integer(base.inner, dtype.map(|wrap| wrap.0), strict)
            .into()
    }

    #[cfg(feature = "extract_jsonpath")]
    fn str_json_decode(&self, dtype: PyDataTypeExpr) -> Self {
        self.inner.clone().str().json_decode(dtype.inner).into()
    }

    #[cfg(feature = "extract_jsonpath")]
    fn str_json_path_match(&self, pat: Self) -> Self {
        self.inner.clone().str().json_path_match(pat.inner).into()
    }

    fn str_extract(&self, pat: Self, group_index: usize) -> Self {
        self.inner
            .clone()
            .str()
            .extract(pat.inner, group_index)
            .into()
    }

    fn str_extract_all(&self, pat: Self) -> Self {
        self.inner.clone().str().extract_all(pat.inner).into()
    }

    #[cfg(feature = "extract_groups")]
    fn str_extract_groups(&self, pat: &str) -> PyResult<Self> {
        Ok(self
            .inner
            .clone()
            .str()
            .extract_groups(pat)
            .map_err(PyPolarsErr::from)?
            .into())
    }

    fn str_count_matches(&self, pat: Self, literal: bool) -> Self {
        self.inner
            .clone()
            .str()
            .count_matches(pat.inner, literal)
            .into()
    }

    fn str_split(&self, by: Self) -> Self {
        self.inner.clone().str().split(by.inner).into()
    }

    fn str_split_inclusive(&self, by: Self) -> Self {
        self.inner.clone().str().split_inclusive(by.inner).into()
    }

    fn str_split_exact(&self, by: Self, n: usize) -> Self {
        self.inner.clone().str().split_exact(by.inner, n).into()
    }

    fn str_split_exact_inclusive(&self, by: Self, n: usize) -> Self {
        self.inner
            .clone()
            .str()
            .split_exact_inclusive(by.inner, n)
            .into()
    }

    fn str_splitn(&self, by: Self, n: usize) -> Self {
        self.inner.clone().str().splitn(by.inner, n).into()
    }

    #[cfg(feature = "regex")]
    fn str_split_regex(&self, by: Self, strict: bool) -> Self {
        self.str_split_regex_with_strict(by, strict)
    }

    #[cfg(feature = "regex")]
    fn str_split_regex_inclusive(&self, by: Self, strict: bool) -> Self {
        self.str_split_regex_inclusive_with_strict(by, strict)
    }

    #[cfg(feature = "regex")]
    fn str_split_regex_with_strict(&self, by: Self, strict: bool) -> Self {
        self.inner
            .clone()
            .str()
            .split_regex(by.inner, strict)
            .into()
    }

    #[cfg(feature = "regex")]
    fn str_split_regex_inclusive_with_strict(&self, by: Self, strict: bool) -> Self {
        self.inner
            .clone()
            .str()
            .split_regex_inclusive(by.inner, strict)
            .into()
    }

    fn str_to_decimal(&self, scale: usize) -> Self {
        self.inner.clone().str().to_decimal(scale).into()
    }

    #[cfg(feature = "find_many")]
    fn str_contains_any(&self, patterns: PyExpr, ascii_case_insensitive: bool) -> Self {
        self.inner
            .clone()
            .str()
            .contains_any(patterns.inner, ascii_case_insensitive)
            .into()
    }
    #[cfg(feature = "find_many")]
    fn str_replace_many(
        &self,
        patterns: PyExpr,
        replace_with: PyExpr,
        ascii_case_insensitive: bool,
        leftmost: bool,
    ) -> Self {
        self.inner
            .clone()
            .str()
            .replace_many(
                patterns.inner,
                replace_with.inner,
                ascii_case_insensitive,
                leftmost,
            )
            .into()
    }

    #[cfg(feature = "find_many")]
    fn str_extract_many(
        &self,
        patterns: PyExpr,
        ascii_case_insensitive: bool,
        overlapping: bool,
        leftmost: bool,
    ) -> Self {
        self.inner
            .clone()
            .str()
            .extract_many(
                patterns.inner,
                ascii_case_insensitive,
                overlapping,
                leftmost,
            )
            .into()
    }

    #[cfg(feature = "find_many")]
    fn str_find_many(
        &self,
        patterns: PyExpr,
        ascii_case_insensitive: bool,
        overlapping: bool,
        leftmost: bool,
    ) -> Self {
        self.inner
            .clone()
            .str()
            .find_many(
                patterns.inner,
                ascii_case_insensitive,
                overlapping,
                leftmost,
            )
            .into()
    }

    #[cfg(feature = "regex")]
    fn str_escape_regex(&self) -> Self {
        self.inner.clone().str().escape_regex().into()
    }

    #[staticmethod]
    fn str_format(f_string: String, exprs: Vec<PyExpr>) -> PyResult<Self> {
        let exprs = exprs.into_iter().map(|e| e.inner).collect::<Vec<_>>();
        Ok(format_str(&f_string, exprs)
            .map_err(PyPolarsErr::from)?
            .into())
    }
}
