use std::str::from_utf8;

use pyo3::intern;
use pyo3::prelude::*;

use pyo3::sync::PyOnceLock;
use pyo3::types::PyType;
use pyo3::types::{
    PyBool, PyByteArray, PyBytes, PyComplex, PyDate, PyDateTime, PyDict, PyFloat, PyFrozenSet, PyInt, PyIterator,
    PyList, PyMapping, PySet, PyString, PyTime, PyTuple,
};

use pyo3::PyTypeCheck;
use pyo3::PyTypeInfo;
use speedate::MicrosecondsPrecisionOverflowBehavior;

use crate::ArgsKwargs;
use crate::errors::{ErrorType, ErrorTypeDefaults, InputValue, LocItem, ValError, ValResult};
use crate::lookup_key::LookupPath;
use crate::tools::safe_repr;
use crate::validators::Exactness;
use crate::validators::TemporalUnitMode;
use crate::validators::ValBytesMode;
use crate::validators::complex::{get_complex_type, string_to_complex};
use crate::validators::decimal::{create_decimal, get_decimal_type};

use super::Arguments;
use super::ConsumeIterator;
use super::KeywordArgs;
use super::PositionalArgs;
use super::ValidatedDict;
use super::ValidatedList;
use super::ValidatedSet;
use super::ValidatedTuple;
use super::datetime::{
    EitherDate, EitherDateTime, EitherTime, bytes_as_date, bytes_as_datetime, bytes_as_time, bytes_as_timedelta,
    date_as_datetime, float_as_datetime, float_as_duration, float_as_time, int_as_datetime, int_as_duration,
    int_as_time,
};
use super::input_abstract::ValMatch;
use super::return_enums::EitherComplex;
use super::return_enums::{ValidationMatch, iterate_attributes, iterate_mapping_items};
use super::shared::{
    decimal_as_int, float_as_int, fraction_as_int, get_enum_meta_object, int_as_bool, str_as_bool, str_as_float,
    str_as_int,
};
use super::{
    BorrowInput, EitherBytes, EitherFloat, EitherInt, EitherString, EitherTimedelta, GenericIterator, Input,
    py_string_str,
};

static FRACTION_TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();

pub fn get_fraction_type(py: Python<'_>) -> &Bound<'_, PyType> {
    FRACTION_TYPE
        .get_or_init(py, || {
            py.import("fractions")
                .and_then(|fractions_module| fractions_module.getattr("Fraction"))
                .unwrap()
                .extract()
                .unwrap()
        })
        .bind(py)
}

pub(crate) fn downcast_python_input<'py, T: PyTypeCheck>(input: &(impl Input<'py> + ?Sized)) -> Option<&Bound<'py, T>> {
    input.as_python().and_then(|any| any.cast::<T>().ok())
}

pub(crate) fn input_as_python_instance<'a, 'py>(
    input: &'a (impl Input<'py> + ?Sized),
    class: &Bound<'py, PyType>,
) -> Option<&'a Bound<'py, PyAny>> {
    input.as_python().filter(|any| any.is_instance(class).unwrap_or(false))
}

impl From<&Bound<'_, PyAny>> for LocItem {
    fn from(py_any: &Bound<'_, PyAny>) -> Self {
        if let Ok(py_str) = py_any.cast::<PyString>() {
            py_str.to_string_lossy().as_ref().into()
        } else if let Ok(key_int) = py_any.extract::<i64>() {
            key_int.into()
        } else {
            safe_repr(py_any).to_string().into()
        }
    }
}

impl From<Bound<'_, PyAny>> for LocItem {
    fn from(py_any: Bound<'_, PyAny>) -> Self {
        (&py_any).into()
    }
}

impl<'py> Input<'py> for Bound<'py, PyAny> {
    #[inline]
    fn py_converter(&self) -> impl IntoPyObject<'py> + '_ {
        self
    }

    fn as_error_value(&self) -> InputValue {
        InputValue::Python(self.clone().into())
    }

    fn is_none(&self) -> bool {
        PyAnyMethods::is_none(self)
    }

    fn as_python(&self) -> Option<&Bound<'py, PyAny>> {
        Some(self)
    }

    fn as_kwargs(&self, _py: Python<'py>) -> Option<Bound<'py, PyDict>> {
        self.cast::<PyDict>().ok().map(Bound::to_owned)
    }

    type Arguments<'a>
        = PyArgs<'py>
    where
        Self: 'a;

    fn validate_args(&self) -> ValResult<PyArgs<'py>> {
        if let Ok(dict) = self.cast::<PyDict>() {
            Ok(PyArgs::new(None, Some(dict.clone())))
        } else if let Ok(args_kwargs) = self.extract::<ArgsKwargs>() {
            let args = args_kwargs.args.into_bound(self.py());
            let kwargs = args_kwargs.kwargs.map(|d| d.into_bound(self.py()));
            Ok(PyArgs::new(Some(args), kwargs))
        } else if let Ok(tuple) = self.cast::<PyTuple>() {
            Ok(PyArgs::new(Some(tuple.clone()), None))
        } else if let Ok(list) = self.cast::<PyList>() {
            Ok(PyArgs::new(Some(list.to_tuple()), None))
        } else {
            Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self))
        }
    }

    fn validate_args_v3(&self) -> ValResult<PyArgs<'py>> {
        if let Ok(args_kwargs) = self.extract::<ArgsKwargs>() {
            let args = args_kwargs.args.into_bound(self.py());
            let kwargs = args_kwargs.kwargs.map(|d| d.into_bound(self.py()));
            Ok(PyArgs::new(Some(args), kwargs))
        } else {
            Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self))
        }
    }

    fn validate_dataclass_args<'a>(&'a self, class_name: &str) -> ValResult<PyArgs<'py>> {
        if let Ok(dict) = self.cast::<PyDict>() {
            Ok(PyArgs::new(None, Some(dict.clone())))
        } else if let Ok(args_kwargs) = self.extract::<ArgsKwargs>() {
            let args = args_kwargs.args.into_bound(self.py());
            let kwargs = args_kwargs.kwargs.map(|d| d.into_bound(self.py()));
            Ok(PyArgs::new(Some(args), kwargs))
        } else {
            let class_name = class_name.to_string();
            Err(ValError::new(
                ErrorType::DataclassType {
                    class_name,
                    context: None,
                },
                self,
            ))
        }
    }

    fn validate_str(
        &self,
        strict: bool,
        coerce_numbers_to_str: bool,
    ) -> ValResult<ValidationMatch<EitherString<'_, 'py>>> {
        if let Ok(py_str) = self.cast_exact::<PyString>() {
            return Ok(ValidationMatch::exact(py_str.clone().into()));
        } else if let Ok(py_str) = self.cast::<PyString>() {
            // force to a rust string to make sure behavior is consistent whether or not we go via a
            // rust string in StrConstrainedValidator - e.g. to_lower
            return Ok(ValidationMatch::strict(py_string_str(py_str)?.into()));
        }

        'lax: {
            if !strict {
                return if let Ok(bytes) = self.cast::<PyBytes>() {
                    match from_utf8(bytes.as_bytes()) {
                        Ok(str) => Ok(str.into()),
                        Err(_) => Err(ValError::new(ErrorTypeDefaults::StringUnicode, self)),
                    }
                } else if let Ok(py_byte_array) = self.cast::<PyByteArray>() {
                    match bytearray_to_str(py_byte_array) {
                        Ok(py_str) => Ok(py_str.into()),
                        Err(_) => Err(ValError::new(ErrorTypeDefaults::StringUnicode, self)),
                    }
                } else if coerce_numbers_to_str && !self.is_exact_instance_of::<PyBool>() && {
                    let py = self.py();
                    let decimal_type = get_decimal_type(py);

                    // only allow int, float, and decimal (not bool)
                    self.is_instance_of::<PyInt>()
                        || self.is_instance_of::<PyFloat>()
                        || self.is_instance(decimal_type).unwrap_or_default()
                } {
                    Ok(self.str()?.into())
                } else if let Some(enum_val) = maybe_as_enum(self) {
                    Ok(enum_val.str()?.into())
                } else {
                    break 'lax;
                }
                .map(ValidationMatch::lax);
            }
        }

        Err(ValError::new(ErrorTypeDefaults::StringType, self))
    }

    fn validate_bytes<'a>(
        &'a self,
        strict: bool,
        mode: ValBytesMode,
    ) -> ValResult<ValidationMatch<EitherBytes<'a, 'py>>> {
        if let Ok(py_bytes) = self.cast_exact::<PyBytes>() {
            return Ok(ValidationMatch::exact(py_bytes.into()));
        } else if let Ok(py_bytes) = self.cast::<PyBytes>() {
            return Ok(ValidationMatch::strict(py_bytes.into()));
        }

        'lax: {
            if !strict {
                return if let Ok(py_str) = self.cast::<PyString>() {
                    let str = py_string_str(py_str)?;
                    match mode.deserialize_string(str) {
                        Ok(b) => Ok(b),
                        Err(e) => Err(ValError::new(e, self)),
                    }
                } else if let Ok(py_byte_array) = self.cast::<PyByteArray>() {
                    Ok(py_byte_array.to_vec().into())
                } else {
                    break 'lax;
                }
                .map(ValidationMatch::lax);
            }
        }

        Err(ValError::new(ErrorTypeDefaults::BytesType, self))
    }

    fn validate_bool(&self, strict: bool) -> ValResult<ValidationMatch<bool>> {
        if let Ok(bool) = self.cast::<PyBool>() {
            return Ok(ValidationMatch::exact(bool.is_true()));
        }

        if !strict {
            if let Some(s) = maybe_as_string(self, ErrorTypeDefaults::BoolParsing)? {
                return str_as_bool(self, s).map(ValidationMatch::lax);
            } else if let Ok(int) = self.extract() {
                return int_as_bool(self, int).map(ValidationMatch::lax);
            } else if let Ok(float) = self.extract::<f64>() {
                if let Ok(int) = float_as_int(self, float) {
                    return int
                        .as_bool()
                        .ok_or_else(|| ValError::new(ErrorTypeDefaults::BoolParsing, self))
                        .map(ValidationMatch::lax);
                }
            }
        }

        Err(ValError::new(ErrorTypeDefaults::BoolType, self))
    }

    fn validate_int(&self, strict: bool) -> ValResult<ValidationMatch<EitherInt<'_>>> {
        if self.is_exact_instance_of::<PyInt>() {
            return Ok(ValidationMatch::exact(EitherInt::Py(self.clone())));
        } else if self.is_instance_of::<PyInt>() {
            // bools are a subclass of int, so check for bool type in this specific case
            let exactness = if self.is_instance_of::<PyBool>() {
                if strict {
                    return Err(ValError::new(ErrorTypeDefaults::IntType, self));
                }
                Exactness::Lax
            } else {
                Exactness::Strict
            };

            // force to an int to upcast to a pure python int
            return EitherInt::upcast(self).map(|either_int| ValidationMatch::new(either_int, exactness));
        }

        'lax: {
            if !strict {
                return if let Some(s) = maybe_as_string(self, ErrorTypeDefaults::IntParsing)? {
                    str_as_int(self, s)
                } else if self.is_exact_instance_of::<PyFloat>() {
                    float_as_int(self, self.extract::<f64>()?)
                } else if let Ok(decimal) = self.validate_decimal(true, self.py()) {
                    decimal_as_int(self, &decimal.into_inner())
                } else if self.is_instance(get_fraction_type(self.py()))? {
                    fraction_as_int(self)
                } else if let Ok(float) = self.extract::<f64>() {
                    float_as_int(self, float)
                } else if let Some(enum_val) = maybe_as_enum(self) {
                    Ok(EitherInt::Py(enum_val))
                } else {
                    break 'lax;
                }
                .map(ValidationMatch::lax);
            }
        }

        Err(ValError::new(ErrorTypeDefaults::IntType, self))
    }

    fn exact_int(&self) -> ValResult<EitherInt<'_>> {
        if self.is_exact_instance_of::<PyInt>() {
            Ok(EitherInt::Py(self.clone()))
        } else {
            Err(ValError::new(ErrorTypeDefaults::IntType, self))
        }
    }

    fn exact_str(&self) -> ValResult<EitherString<'_, 'py>> {
        if let Ok(py_str) = self.cast_exact() {
            Ok(EitherString::Py(py_str.clone()))
        } else {
            Err(ValError::new(ErrorTypeDefaults::IntType, self))
        }
    }

    fn validate_float(&self, strict: bool) -> ValResult<ValidationMatch<EitherFloat<'_>>> {
        if let Ok(float) = self.cast_exact::<PyFloat>() {
            return Ok(ValidationMatch::exact(EitherFloat::Py(float.clone())));
        }

        if !strict {
            if let Some(s) = maybe_as_string(self, ErrorTypeDefaults::FloatParsing)? {
                // checking for bytes and string is fast, so do this before isinstance(float)
                return str_as_float(self, s).map(ValidationMatch::lax);
            }
        }

        if let Ok(float) = self.extract::<f64>() {
            let exactness = if self.is_instance_of::<PyBool>() {
                if strict {
                    return Err(ValError::new(ErrorTypeDefaults::FloatType, self));
                }
                Exactness::Lax
            } else {
                Exactness::Strict
            };
            return Ok(ValidationMatch::new(EitherFloat::F64(float), exactness));
        }

        Err(ValError::new(ErrorTypeDefaults::FloatType, self))
    }

    fn validate_decimal(&self, strict: bool, py: Python<'py>) -> ValMatch<Bound<'py, PyAny>> {
        let decimal_type = get_decimal_type(py);

        // Fast path for existing decimal objects
        if self.is_exact_instance(decimal_type) {
            return Ok(ValidationMatch::exact(self.to_owned().clone()));
        }

        if !strict {
            if self.is_instance_of::<PyString>() || (self.is_instance_of::<PyInt>() && !self.is_instance_of::<PyBool>())
            {
                // Checking isinstance for str / int / bool is fast compared to decimal / float
                return create_decimal(self, self).map(ValidationMatch::lax);
            }

            if self.is_instance_of::<PyFloat>() {
                return create_decimal(self.str()?.as_any(), self).map(ValidationMatch::lax);
            }

            // Handle three-tuple constructor: (sign, digits_tuple, exponent)
            if let Ok(tuple) = self.cast_exact::<PyTuple>()
                && tuple.len() == 3
                && let Ok(decimal) = create_decimal(self, self)
            {
                return Ok(ValidationMatch::lax(decimal));
            }
        }

        if self.is_instance(decimal_type)? {
            // Upcast subclasses to decimal
            return create_decimal(self, self).map(ValidationMatch::strict);
        }

        let error_type = if strict {
            ErrorType::IsInstanceOf {
                class: decimal_type
                    .qualname()
                    .and_then(|name| name.extract())
                    .unwrap_or_else(|_| "Decimal".to_owned()),
                context: None,
            }
        } else {
            ErrorTypeDefaults::DecimalType
        };

        Err(ValError::new(error_type, self))
    }

    type Dict<'a>
        = GenericPyMapping<'a, 'py>
    where
        Self: 'a;

    fn strict_dict<'a>(&'a self) -> ValResult<GenericPyMapping<'a, 'py>> {
        if let Ok(dict) = self.cast_exact::<PyDict>() {
            Ok(GenericPyMapping::Dict(dict))
        } else if self.is_instance_of::<PyDict>() {
            Ok(GenericPyMapping::Mapping(self.cast::<PyMapping>()?))
        } else {
            Err(ValError::new(ErrorTypeDefaults::DictType, self))
        }
    }

    fn lax_dict<'a>(&'a self) -> ValResult<GenericPyMapping<'a, 'py>> {
        if let Ok(dict) = self.cast_exact::<PyDict>() {
            Ok(GenericPyMapping::Dict(dict))
        } else if let Ok(mapping) = self.cast::<PyMapping>() {
            Ok(GenericPyMapping::Mapping(mapping))
        } else {
            Err(ValError::new(ErrorTypeDefaults::DictType, self))
        }
    }

    fn validate_model_fields<'a>(
        &'a self,
        strict: bool,
        from_attributes: bool,
    ) -> ValResult<GenericPyMapping<'a, 'py>> {
        if from_attributes {
            // if from_attributes, first try a dict, then mapping then from_attributes
            if let Ok(dict) = self.cast::<PyDict>() {
                return Ok(GenericPyMapping::Dict(dict));
            } else if !strict {
                if let Ok(mapping) = self.cast::<PyMapping>() {
                    return Ok(GenericPyMapping::Mapping(mapping));
                }
            }

            if from_attributes_applicable(self) {
                Ok(GenericPyMapping::GetAttr(self.to_owned(), None))
            } else if let Ok((obj, kwargs)) = self.extract() {
                if from_attributes_applicable(&obj) {
                    Ok(GenericPyMapping::GetAttr(obj, Some(kwargs)))
                } else {
                    Err(ValError::new(ErrorTypeDefaults::ModelAttributesType, self))
                }
            } else {
                // note the error here gives a hint about from_attributes
                Err(ValError::new(ErrorTypeDefaults::ModelAttributesType, self))
            }
        } else {
            // otherwise we just call back to validate_dict if from_mapping is allowed, note that errors in this
            // case (correctly) won't hint about from_attributes
            self.validate_dict(strict)
        }
    }

    type List<'a>
        = PySequenceIterable<'a, 'py>
    where
        Self: 'a;

    fn validate_list<'a>(&'a self, strict: bool) -> ValMatch<PySequenceIterable<'a, 'py>> {
        if let Ok(list) = self.cast::<PyList>() {
            return Ok(ValidationMatch::exact(PySequenceIterable::List(list)));
        } else if !strict && let Ok(other) = extract_sequence_iterable(self) {
            return Ok(ValidationMatch::lax(other));
        }

        Err(ValError::new(ErrorTypeDefaults::ListType, self))
    }

    type Tuple<'a>
        = PySequenceIterable<'a, 'py>
    where
        Self: 'a;

    fn validate_tuple<'a>(&'a self, strict: bool) -> ValMatch<PySequenceIterable<'a, 'py>> {
        if let Ok(tup) = self.cast::<PyTuple>() {
            return Ok(ValidationMatch::exact(PySequenceIterable::Tuple(tup)));
        } else if !strict && let Ok(other) = extract_sequence_iterable(self) {
            return Ok(ValidationMatch::lax(other));
        }

        Err(ValError::new(ErrorTypeDefaults::TupleType, self))
    }

    type Set<'a>
        = PySequenceIterable<'a, 'py>
    where
        Self: 'a;

    fn validate_set<'a>(&'a self, strict: bool) -> ValMatch<PySequenceIterable<'a, 'py>> {
        if let Ok(set) = self.cast::<PySet>() {
            return Ok(ValidationMatch::exact(PySequenceIterable::Set(set)));
        } else if !strict && let Ok(other) = extract_sequence_iterable(self) {
            return Ok(ValidationMatch::lax(other));
        }

        Err(ValError::new(ErrorTypeDefaults::SetType, self))
    }

    fn validate_frozenset<'a>(&'a self, strict: bool) -> ValMatch<PySequenceIterable<'a, 'py>> {
        if let Ok(frozenset) = self.cast::<PyFrozenSet>() {
            return Ok(ValidationMatch::exact(PySequenceIterable::FrozenSet(frozenset)));
        } else if !strict && let Ok(other) = extract_sequence_iterable(self) {
            return Ok(ValidationMatch::lax(other));
        }

        Err(ValError::new(ErrorTypeDefaults::FrozenSetType, self))
    }

    fn validate_iter(&self) -> ValResult<GenericIterator<'static>> {
        if self.try_iter().is_ok() {
            Ok(self.into())
        } else {
            Err(ValError::new(ErrorTypeDefaults::IterableType, self))
        }
    }

    fn validate_date(&self, strict: bool, mode: TemporalUnitMode) -> ValResult<ValidationMatch<EitherDate<'py>>> {
        if let Ok(date) = self.cast_exact::<PyDate>() {
            Ok(ValidationMatch::exact(date.clone().into()))
        } else if self.is_instance_of::<PyDateTime>() {
            // have to check if it's a datetime first, otherwise the line below converts to a date
            // even if we later try coercion from a datetime, we don't want to return a datetime now
            Err(ValError::new(ErrorTypeDefaults::DateType, self))
        } else if let Ok(date) = self.cast::<PyDate>() {
            Ok(ValidationMatch::strict(date.clone().into()))
        } else if let Some(bytes) = {
            if strict {
                None
            } else if let Ok(py_str) = self.cast::<PyString>() {
                let str = py_string_str(py_str)?;
                Some(str.as_bytes())
            } else if let Ok(py_bytes) = self.cast::<PyBytes>() {
                Some(py_bytes.as_bytes())
            } else {
                None
            }
        } {
            bytes_as_date(self, bytes, mode).map(ValidationMatch::lax)
        } else {
            Err(ValError::new(ErrorTypeDefaults::DateType, self))
        }
    }

    fn validate_time(
        &self,
        strict: bool,
        microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
    ) -> ValResult<ValidationMatch<EitherTime<'py>>> {
        if let Ok(time) = self.cast_exact::<PyTime>() {
            return Ok(ValidationMatch::exact(time.clone().into()));
        } else if let Ok(time) = self.cast::<PyTime>() {
            return Ok(ValidationMatch::strict(time.clone().into()));
        }

        'lax: {
            if !strict {
                return if let Ok(py_str) = self.cast::<PyString>() {
                    let str = py_string_str(py_str)?;
                    bytes_as_time(self, str.as_bytes(), microseconds_overflow_behavior)
                } else if let Ok(py_bytes) = self.cast::<PyBytes>() {
                    bytes_as_time(self, py_bytes.as_bytes(), microseconds_overflow_behavior)
                } else if self.is_exact_instance_of::<PyBool>() {
                    Err(ValError::new(ErrorTypeDefaults::TimeType, self))
                } else if let Ok(int) = self.extract() {
                    int_as_time(self, int, 0)
                } else if let Ok(float) = self.extract::<f64>() {
                    float_as_time(self, float)
                } else {
                    break 'lax;
                }
                .map(ValidationMatch::lax);
            }
        }

        Err(ValError::new(ErrorTypeDefaults::TimeType, self))
    }

    fn validate_datetime(
        &self,
        strict: bool,
        microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
        mode: TemporalUnitMode,
    ) -> ValResult<ValidationMatch<EitherDateTime<'py>>> {
        if let Ok(dt) = self.cast_exact::<PyDateTime>() {
            return Ok(ValidationMatch::exact(dt.clone().into()));
        } else if let Ok(dt) = self.cast::<PyDateTime>() {
            return Ok(ValidationMatch::strict(dt.clone().into()));
        }

        'lax: {
            if !strict {
                return if let Ok(py_str) = self.cast::<PyString>() {
                    let str = py_string_str(py_str)?;
                    bytes_as_datetime(self, str.as_bytes(), microseconds_overflow_behavior, mode)
                } else if let Ok(py_bytes) = self.cast::<PyBytes>() {
                    bytes_as_datetime(self, py_bytes.as_bytes(), microseconds_overflow_behavior, mode)
                } else if self.is_exact_instance_of::<PyBool>() {
                    Err(ValError::new(ErrorTypeDefaults::DatetimeType, self))
                } else if let Ok(int) = self.extract() {
                    int_as_datetime(self, int, 0, mode)
                } else if let Ok(float) = self.extract::<f64>() {
                    float_as_datetime(self, float, mode)
                } else if let Ok(date) = self.cast::<PyDate>() {
                    Ok(date_as_datetime(date)?)
                } else {
                    break 'lax;
                }
                .map(ValidationMatch::lax);
            }
        }

        Err(ValError::new(ErrorTypeDefaults::DatetimeType, self))
    }

    fn validate_timedelta(
        &self,
        strict: bool,
        microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
    ) -> ValResult<ValidationMatch<EitherTimedelta<'py>>> {
        if let Ok(either_dt) = EitherTimedelta::try_from(self) {
            let exactness = if matches!(either_dt, EitherTimedelta::PyExact(_)) {
                Exactness::Exact
            } else {
                Exactness::Strict
            };
            return Ok(ValidationMatch::new(either_dt, exactness));
        }

        'lax: {
            if !strict {
                return if let Ok(py_str) = self.cast::<PyString>() {
                    let str = py_string_str(py_str)?;
                    bytes_as_timedelta(self, str.as_bytes(), microseconds_overflow_behavior)
                } else if let Ok(py_bytes) = self.cast::<PyBytes>() {
                    bytes_as_timedelta(self, py_bytes.as_bytes(), microseconds_overflow_behavior)
                } else if let Ok(int) = self.extract() {
                    Ok(int_as_duration(self, int)?.into())
                } else if let Ok(float) = self.extract::<f64>() {
                    Ok(float_as_duration(self, float)?.into())
                } else {
                    break 'lax;
                }
                .map(ValidationMatch::lax);
            }
        }

        Err(ValError::new(ErrorTypeDefaults::TimeDeltaType, self))
    }

    fn validate_complex<'a>(&'a self, strict: bool, py: Python<'py>) -> ValResult<ValidationMatch<EitherComplex<'py>>> {
        if let Ok(complex) = self.cast::<PyComplex>() {
            return Ok(ValidationMatch::strict(EitherComplex::Py(complex.to_owned())));
        }
        if strict {
            return Err(ValError::new(
                ErrorType::IsInstanceOf {
                    class: PyComplex::type_object(py)
                        .qualname()
                        .and_then(|name| name.extract())
                        .unwrap_or_else(|_| "complex".to_owned()),
                    context: None,
                },
                self,
            ));
        }

        if let Ok(s) = self.cast::<PyString>()
            && let Ok(c) = string_to_complex(s, self)
        {
            // If input is not a valid complex string, instead of telling users to correct
            // the string, it makes more sense to tell them to provide any acceptable value
            // since they might have just given values of some incorrect types instead
            // of actually trying some complex strings.
            return Ok(ValidationMatch::lax(EitherComplex::Py(c)));
        }

        // Delegate to the constructor directly
        // (see https://docs.python.org/3/library/functions.html#complex):
        if let Ok(complex_obj) = get_complex_type(py).call1((self,))
            && let Ok(complex) = complex_obj.cast_into()
        {
            return Ok(ValidationMatch::lax(EitherComplex::Py(complex)));
        }

        Err(ValError::new(ErrorTypeDefaults::ComplexType, self))
    }
}

impl<'py> BorrowInput<'py> for Bound<'py, PyAny> {
    type Input = Bound<'py, PyAny>;
    fn borrow_input(&self) -> &Self::Input {
        self
    }
}

impl<'py> BorrowInput<'py> for Borrowed<'_, 'py, PyAny> {
    type Input = Bound<'py, PyAny>;
    fn borrow_input(&self) -> &Self::Input {
        self
    }
}

/// Best effort check of whether it's likely to make sense to inspect obj for attributes and iterate over it
/// with `obj.dir()`
fn from_attributes_applicable(obj: &Bound<'_, PyAny>) -> bool {
    let Some(module_name) = obj
        .get_type()
        .getattr(intern!(obj.py(), "__module__"))
        .ok()
        .and_then(|module_name| module_name.cast_into::<PyString>().ok())
    else {
        return false;
    };
    // I don't think it's a very good list at all! But it doesn't have to be at perfect, it just needs to avoid
    // the most egregious foot guns, it's mostly just to catch "builtins"
    // still happy to add more or do something completely different if anyone has a better idea???
    // dbg!(obj, &module_name);
    !matches!(module_name.to_str(), Ok("builtins" | "datetime" | "collections"))
}

/// Utility for extracting a string from a PyAny, if possible.
fn maybe_as_string<'a>(v: &'a Bound<'_, PyAny>, unicode_error: ErrorType) -> ValResult<Option<&'a str>> {
    if let Ok(py_string) = v.cast::<PyString>() {
        py_string_str(py_string).map(Some)
    } else if let Ok(bytes) = v.cast::<PyBytes>() {
        match from_utf8(bytes.as_bytes()) {
            Ok(s) => Ok(Some(s)),
            Err(_) => Err(ValError::new(unicode_error, v)),
        }
    } else {
        Ok(None)
    }
}

/// Decode a Python bytearray to a Python string.
///
/// Using Python's built-in machinery for this should be efficient and avoids questions around
/// safety of concurrent mutation of the bytearray (by leaving that to the Python interpreter).
fn bytearray_to_str<'py>(bytearray: &Bound<'py, PyByteArray>) -> PyResult<Bound<'py, PyString>> {
    let py = bytearray.py();
    let py_string = bytearray
        .call_method1(intern!(py, "decode"), (intern!(py, "utf-8"),))?
        .cast_into()?;
    Ok(py_string)
}

/// Utility for extracting an enum value, if possible.
fn maybe_as_enum<'py>(v: &Bound<'py, PyAny>) -> Option<Bound<'py, PyAny>> {
    let py = v.py();
    let enum_meta_object = get_enum_meta_object(py);
    let meta_type = v.get_type().get_type();
    if meta_type.is(enum_meta_object) {
        v.getattr(intern!(py, "value")).ok()
    } else {
        None
    }
}

#[cfg_attr(debug_assertions, derive(Debug))]
pub struct PyArgs<'py> {
    pub args: Option<PyPosArgs<'py>>,
    pub kwargs: Option<PyKwargs<'py>>,
}

#[cfg_attr(debug_assertions, derive(Debug))]
pub struct PyPosArgs<'py>(Bound<'py, PyTuple>);
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct PyKwargs<'py>(Bound<'py, PyDict>);

impl<'py> PyArgs<'py> {
    pub fn new(args: Option<Bound<'py, PyTuple>>, kwargs: Option<Bound<'py, PyDict>>) -> Self {
        Self {
            args: args.map(PyPosArgs),
            kwargs: kwargs.map(PyKwargs),
        }
    }
}

impl<'py> Arguments<'py> for PyArgs<'py> {
    type Args = PyPosArgs<'py>;
    type Kwargs = PyKwargs<'py>;

    fn args(&self) -> Option<&PyPosArgs<'py>> {
        self.args.as_ref()
    }

    fn kwargs(&self) -> Option<&PyKwargs<'py>> {
        self.kwargs.as_ref()
    }
}

impl<'py> PositionalArgs<'py> for PyPosArgs<'py> {
    type Item<'a>
        = Borrowed<'a, 'py, PyAny>
    where
        Self: 'a;

    fn len(&self) -> usize {
        self.0.len()
    }

    fn get_item(&self, index: usize) -> Option<Self::Item<'_>> {
        self.0.get_borrowed_item(index).ok()
    }

    fn iter(&self) -> impl Iterator<Item = Self::Item<'_>> {
        self.0.iter_borrowed()
    }
}

impl<'py> KeywordArgs<'py> for PyKwargs<'py> {
    type Key<'a>
        = Bound<'py, PyAny>
    where
        Self: 'a;

    type Item<'a>
        = Bound<'py, PyAny>
    where
        Self: 'a;

    fn len(&self) -> usize {
        self.0.len()
    }

    fn get_item<'k>(&self, key: &LookupPath) -> ValResult<Option<Self::Item<'_>>> {
        key.py_get_dict_item(&self.0).map_err(Into::into)
    }

    fn iter(&self) -> impl Iterator<Item = ValResult<(Self::Key<'_>, Self::Item<'_>)>> {
        self.0.iter().map(Ok)
    }
}

#[cfg_attr(debug_assertions, derive(Debug))]
pub enum GenericPyMapping<'a, 'py> {
    Dict(&'a Bound<'py, PyDict>),
    Mapping(&'a Bound<'py, PyMapping>),
    GetAttr(Bound<'py, PyAny>, Option<Bound<'py, PyDict>>),
}

impl<'py> ValidatedDict<'py> for GenericPyMapping<'_, 'py> {
    type Key<'a>
        = Bound<'py, PyAny>
    where
        Self: 'a;

    type Item<'a>
        = Bound<'py, PyAny>
    where
        Self: 'a;

    fn get_item(&self, key: &LookupPath) -> ValResult<Option<Self::Item<'_>>> {
        match self {
            Self::Dict(dict) => key.py_get_dict_item(dict).map_err(Into::into),
            Self::Mapping(mapping) => key.py_get_mapping_item(mapping).map_err(Into::into),
            Self::GetAttr(obj, dict) => key.py_get_attr(obj, dict.as_ref()),
        }
    }

    fn is_py_get_attr(&self) -> bool {
        matches!(self, Self::GetAttr(..))
    }

    fn iterate<'a, R>(
        &'a self,
        consumer: impl ConsumeIterator<ValResult<(Self::Key<'a>, Self::Item<'a>)>, Output = R>,
    ) -> ValResult<R> {
        match self {
            Self::Dict(dict) => Ok(consumer.consume_iterator(dict.iter().map(Ok))),
            Self::Mapping(mapping) => Ok(consumer.consume_iterator(iterate_mapping_items(mapping)?)),
            Self::GetAttr(obj, _) => Ok(consumer.consume_iterator(iterate_attributes(obj)?)),
        }
    }

    fn last_key(&self) -> Option<Self::Key<'_>> {
        match self {
            Self::Dict(dict) => dict.keys().iter().last(),
            // see https://github.com/pydantic/pydantic-core/pull/1512#discussion_r1826057970
            Self::Mapping(mapping) => mapping
                .call_method0(intern!(mapping.py(), "keys"))
                .ok()?
                .try_iter()
                .ok()?
                .last()?
                .ok(),
            Self::GetAttr(_, _) => None,
        }
    }
}

/// Container for all the collections (sized iterable containers) types, which
/// can mostly be converted to each other in lax mode.
/// This mostly matches python's definition of `Collection`.
pub enum PySequenceIterable<'a, 'py> {
    List(&'a Bound<'py, PyList>),
    Tuple(&'a Bound<'py, PyTuple>),
    Set(&'a Bound<'py, PySet>),
    FrozenSet(&'a Bound<'py, PyFrozenSet>),
    Iterator(Bound<'py, PyIterator>),
}

/// Extract types which can be iterated to produce a sequence-like container like a list, tuple, set
/// or frozenset
fn extract_sequence_iterable<'a, 'py>(obj: &'a Bound<'py, PyAny>) -> ValResult<PySequenceIterable<'a, 'py>> {
    // Handle concrete non-overlapping types first, then abstract types
    if let Ok(iterable) = obj.cast::<PyList>() {
        Ok(PySequenceIterable::List(iterable))
    } else if let Ok(iterable) = obj.cast::<PyTuple>() {
        Ok(PySequenceIterable::Tuple(iterable))
    } else if let Ok(iterable) = obj.cast::<PySet>() {
        Ok(PySequenceIterable::Set(iterable))
    } else if let Ok(iterable) = obj.cast::<PyFrozenSet>() {
        Ok(PySequenceIterable::FrozenSet(iterable))
    } else {
        // Try to get this as a generable iterable thing, but exclude string and mapping types
        if !(obj.is_instance_of::<PyString>()
            || obj.is_instance_of::<PyBytes>()
            || obj.is_instance_of::<PyByteArray>()
            || obj.is_instance_of::<PyDict>()
            || obj.cast::<PyMapping>().is_ok())
            && let Ok(iter) = obj.try_iter()
        {
            return Ok(PySequenceIterable::Iterator(iter));
        }

        Err(ValError::new(ErrorTypeDefaults::IterableType, obj))
    }
}

impl<'py> PySequenceIterable<'_, 'py> {
    pub fn generic_len(&self) -> Option<usize> {
        match &self {
            PySequenceIterable::List(iter) => Some(iter.len()),
            PySequenceIterable::Tuple(iter) => Some(iter.len()),
            PySequenceIterable::Set(iter) => Some(iter.len()),
            PySequenceIterable::FrozenSet(iter) => Some(iter.len()),
            PySequenceIterable::Iterator(iter) => iter.len().ok(),
        }
    }
    fn generic_try_for_each(self, f: impl FnMut(PyResult<Bound<'py, PyAny>>) -> ValResult<()>) -> ValResult<()> {
        match self {
            PySequenceIterable::List(iter) => iter.iter().map(Ok).try_for_each(f),
            PySequenceIterable::Tuple(iter) => iter.iter().map(Ok).try_for_each(f),
            PySequenceIterable::Set(iter) => iter.iter().map(Ok).try_for_each(f),
            PySequenceIterable::FrozenSet(iter) => iter.iter().map(Ok).try_for_each(f),
            PySequenceIterable::Iterator(mut iter) => iter.try_for_each(f),
        }
    }
    fn generic_iterate<R>(
        self,
        consumer: impl ConsumeIterator<PyResult<Bound<'py, PyAny>>, Output = R>,
    ) -> ValResult<R> {
        match self {
            PySequenceIterable::List(iter) => Ok(consumer.consume_iterator(iter.iter().map(Ok))),
            PySequenceIterable::Tuple(iter) => Ok(consumer.consume_iterator(iter.iter().map(Ok))),
            PySequenceIterable::Set(iter) => Ok(consumer.consume_iterator(iter.iter().map(Ok))),
            PySequenceIterable::FrozenSet(iter) => Ok(consumer.consume_iterator(iter.iter().map(Ok))),
            PySequenceIterable::Iterator(iter) => Ok(consumer.consume_iterator(iter.try_iter()?)),
        }
    }
}

impl<'py> ValidatedList<'py> for PySequenceIterable<'_, 'py> {
    type Item = Bound<'py, PyAny>;
    fn len(&self) -> Option<usize> {
        self.generic_len()
    }
    fn iterate<R>(self, consumer: impl ConsumeIterator<PyResult<Self::Item>, Output = R>) -> ValResult<R> {
        self.generic_iterate(consumer)
    }
    fn as_py_list(&self) -> Option<&Bound<'py, PyList>> {
        match self {
            PySequenceIterable::List(iter) => Some(iter),
            _ => None,
        }
    }
}

impl<'py> ValidatedTuple<'py> for PySequenceIterable<'_, 'py> {
    type Item = Bound<'py, PyAny>;
    fn len(&self) -> Option<usize> {
        self.generic_len()
    }
    fn try_for_each(self, f: impl FnMut(PyResult<Self::Item>) -> ValResult<()>) -> ValResult<()> {
        self.generic_try_for_each(f)
    }
    fn iterate<R>(self, consumer: impl ConsumeIterator<PyResult<Self::Item>, Output = R>) -> ValResult<R> {
        self.generic_iterate(consumer)
    }
}

impl<'py> ValidatedSet<'py> for PySequenceIterable<'_, 'py> {
    type Item = Bound<'py, PyAny>;
    fn iterate<R>(self, consumer: impl ConsumeIterator<PyResult<Self::Item>, Output = R>) -> ValResult<R> {
        self.generic_iterate(consumer)
    }
}
