use std::sync::Arc;

use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::sync::PyOnceLock;
use pyo3::types::{PyComplex, PyDict, PyString, PyType};

use crate::build_tools::{LazyLock, is_strict};
use crate::errors::{ErrorTypeDefaults, ToErrorValue, ValError, ValResult};
use crate::input::Input;

use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator};

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

pub fn get_complex_type(py: Python<'_>) -> &Bound<'_, PyType> {
    COMPLEX_TYPE
        .get_or_init(py, || py.get_type::<PyComplex>().into())
        .bind(py)
}

#[derive(Debug)]
pub struct ComplexValidator {
    strict: bool,
}

static STRICT_COMPLEX_VALIDATOR: LazyLock<Arc<CombinedValidator>> =
    LazyLock::new(|| Arc::new(ComplexValidator { strict: true }.into()));

static LAX_COMPLEX_VALIDATOR: LazyLock<Arc<CombinedValidator>> =
    LazyLock::new(|| Arc::new(ComplexValidator { strict: false }.into()));

impl BuildValidator for ComplexValidator {
    const EXPECTED_TYPE: &'static str = "complex";
    fn build(
        schema: &Bound<'_, PyDict>,
        config: Option<&Bound<'_, PyDict>>,
        _definitions: &mut DefinitionsBuilder<Arc<CombinedValidator>>,
    ) -> PyResult<Arc<CombinedValidator>> {
        if is_strict(schema, config)? {
            Ok(STRICT_COMPLEX_VALIDATOR.clone())
        } else {
            Ok(LAX_COMPLEX_VALIDATOR.clone())
        }
    }
}

impl_py_gc_traverse!(ComplexValidator {});

impl Validator for ComplexValidator {
    fn validate<'py>(
        &self,
        py: Python<'py>,
        input: &(impl Input<'py> + ?Sized),
        state: &mut ValidationState<'_, 'py>,
    ) -> ValResult<Py<PyAny>> {
        let res = input.validate_complex(self.strict, py)?.unpack(state);
        Ok(res.into_pyobject(py)?.into())
    }

    fn get_name(&self) -> &'static str {
        "complex"
    }
}

pub(crate) fn string_to_complex<'py>(
    arg: &Bound<'py, PyString>,
    input: impl ToErrorValue,
) -> ValResult<Bound<'py, PyComplex>> {
    let py = arg.py();
    Ok(get_complex_type(py)
        .call1((arg,))
        .map_err(|err| {
            // Since arg is a string, the only possible error here is ValueError
            // triggered by invalid complex strings and thus only this case is handled.
            if err.is_instance_of::<PyValueError>(py) {
                ValError::new(ErrorTypeDefaults::ComplexStrParsing, input)
            } else {
                ValError::InternalErr(err)
            }
        })?
        .cast::<PyComplex>()?
        .to_owned())
}
