use super::SlottedQueryMode;
use std::io::{Error, ErrorKind};

pub(crate) fn resolve_slotted_query_value(
    slot: &str,
    mode: SlottedQueryMode,
    default: Option<&str>,
    provided: Option<&str>,
    min_bytes: Option<usize>,
    max_bytes: Option<usize>,
) -> std::io::Result<Option<String>> {
    let value = match mode {
        SlottedQueryMode::Required => {
            let candidate = match provided {
                Some(v) if !v.is_empty() => Some(v),
                Some(_) | None => default.filter(|v| !v.is_empty()),
            };
            let Some(candidate) = candidate else {
                return Err(Error::new(
                    ErrorKind::InvalidInput,
                    format!("required query slot `{slot}` is missing or empty"),
                ));
            };
            Some(candidate.to_owned())
        }
        SlottedQueryMode::RequiredAllowEmpty => {
            let candidate = match provided {
                Some(v) => Some(v),
                None => default,
            };
            let Some(candidate) = candidate else {
                return Err(Error::new(
                    ErrorKind::InvalidInput,
                    format!("required query slot `{slot}` is missing"),
                ));
            };
            Some(candidate.to_owned())
        }
        SlottedQueryMode::Optional => match provided {
            Some(v) if !v.is_empty() => Some(v.to_owned()),
            Some(_) | None => default.filter(|v| !v.is_empty()).map(ToOwned::to_owned),
        },
        SlottedQueryMode::OptionalAllowEmpty => match provided {
            Some(v) => Some(v.to_owned()),
            None => default.map(ToOwned::to_owned),
        },
    };

    if let Some(ref value) = value {
        validate_byte_len(slot, value, min_bytes, max_bytes)?;
    }

    Ok(value)
}

pub(crate) fn validate_slot_name(slot: &str) -> std::io::Result<()> {
    if slot.is_empty() {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            "slot name must not be empty",
        ));
    }

    if !slot.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_') {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            format!(
                "slot name `{slot}` is invalid: only ASCII letters, digits, and `_` are allowed"
            ),
        ));
    }

    Ok(())
}

pub(crate) fn validate_query_key(key: &str) -> std::io::Result<()> {
    if key.is_empty() {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            "query key must not be empty",
        ));
    }
    Ok(())
}

pub(crate) fn validate_min_max_bounds(
    slot: &str,
    min_bytes: Option<usize>,
    max_bytes: Option<usize>,
) -> std::io::Result<()> {
    if let (Some(min), Some(max)) = (min_bytes, max_bytes)
        && min > max
    {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            format!("slot `{slot}` has invalid byte bounds: min_bytes ({min}) > max_bytes ({max})"),
        ));
    }
    Ok(())
}

pub(crate) fn validate_byte_len(
    slot: &str,
    value: &str,
    min_bytes: Option<usize>,
    max_bytes: Option<usize>,
) -> std::io::Result<()> {
    let len = value.len();
    if let Some(min) = min_bytes
        && len < min
    {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            format!("slot `{slot}` is too short: {len} bytes < min_bytes ({min})"),
        ));
    }
    if let Some(max) = max_bytes
        && len > max
    {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            format!("slot `{slot}` is too long: {len} bytes > max_bytes ({max})"),
        ));
    }
    Ok(())
}
