use std::fmt;
use std::str::FromStr;

use serde::de;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use uuid::{Uuid, Version};

use crate::TypeError;
use crate::checked::{CheckedString, CheckedStringRule, CheckedUuid, UuidVersionRule};

const NANO_ALPHABET: &[u8; 64] =
    b"_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const NANO_DEFAULT_LEN: usize = 21;

macro_rules! impl_uuid_string {
    ($type:ty, $name:literal) => {
        impl FromStr for $type {
            type Err = TypeError;

            fn from_str(value: &str) -> Result<Self, Self::Err> {
                let value = Uuid::parse_str(value).map_err(|error| uuid_error($name, error))?;
                Self::new(value)
            }
        }

        impl fmt::Display for $type {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                self.0.fmt(f)
            }
        }

        impl Serialize for $type {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                serializer.serialize_str(&self.to_string())
            }
        }

        impl<'de> Deserialize<'de> for $type {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: Deserializer<'de>,
            {
                let value = String::deserialize(deserializer)?;
                value.parse().map_err(de::Error::custom)
            }
        }
    };
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub struct NanoIdLength(usize);

impl NanoIdLength {
    pub const DEFAULT: Self = Self(NANO_DEFAULT_LEN);

    pub fn new(value: usize) -> Result<Self, TypeError> {
        if value == 0 {
            return Err(TypeError::empty("NanoIdLength"));
        }
        Ok(Self(value))
    }

    pub const fn get(self) -> usize {
        self.0
    }
}

impl TryFrom<usize> for NanoIdLength {
    type Error = TypeError;

    fn try_from(value: usize) -> Result<Self, Self::Error> {
        Self::new(value)
    }
}

impl FromStr for NanoIdLength {
    type Err = TypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let value = value.parse::<usize>().map_err(|error| {
            TypeError::new(
                "NanoIdLength",
                format!("expected positive length: {error}"),
                "provide a whole number greater than zero",
            )
        })?;
        Self::new(value)
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct UuidId(Uuid);

impl UuidId {
    pub fn new(value: Uuid) -> Result<Self, TypeError> {
        Ok(Self(value))
    }

    pub fn generate_v4() -> Self {
        Self(Uuid::new_v4())
    }

    pub fn generate_v7() -> Self {
        Self(Uuid::now_v7())
    }

    pub fn as_uuid(&self) -> &Uuid {
        &self.0
    }

    pub fn into_uuid(self) -> Uuid {
        self.0
    }
}

impl_uuid_string!(UuidId, "UuidId");

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct UuidV4(CheckedUuid<UuidV4Rule>);

impl UuidV4 {
    pub fn new(value: Uuid) -> Result<Self, TypeError> {
        CheckedUuid::new(value).map(Self)
    }

    pub fn generate() -> Self {
        Self(CheckedUuid::from_valid(Uuid::new_v4()))
    }

    pub fn as_uuid(&self) -> &Uuid {
        self.0.as_uuid()
    }

    pub fn into_uuid(self) -> Uuid {
        self.0.into_uuid()
    }
}

impl_uuid_string!(UuidV4, "UuidV4");

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct UuidV7(CheckedUuid<UuidV7Rule>);

impl UuidV7 {
    pub fn new(value: Uuid) -> Result<Self, TypeError> {
        CheckedUuid::new(value).map(Self)
    }

    pub fn generate() -> Self {
        Self(CheckedUuid::from_valid(Uuid::now_v7()))
    }

    pub fn as_uuid(&self) -> &Uuid {
        self.0.as_uuid()
    }

    pub fn into_uuid(self) -> Uuid {
        self.0.into_uuid()
    }
}

impl_uuid_string!(UuidV7, "UuidV7");

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
    feature = "wire",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent, no_pg_array))]
pub struct NanoId(CheckedString<NanoIdRule>);

impl NanoId {
    pub fn new(value: impl Into<String>) -> Result<Self, TypeError> {
        CheckedString::new(value).map(Self)
    }

    pub fn generate() -> Result<Self, TypeError> {
        Self::generate_with_len(NanoIdLength::DEFAULT)
    }

    pub fn generate_with_len(length: NanoIdLength) -> Result<Self, TypeError> {
        nano_id_from_random(length.get()).and_then(Self::new)
    }

    pub fn len(&self) -> NanoIdLength {
        NanoIdLength(self.0.as_str().len())
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    pub fn into_string(self) -> String {
        self.0.into_string()
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct UuidV4Rule;

impl UuidVersionRule for UuidV4Rule {
    fn type_name() -> &'static str {
        "UuidV4"
    }

    fn version() -> Version {
        Version::Random
    }

    fn version_name() -> &'static str {
        "v4"
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct UuidV7Rule;

impl UuidVersionRule for UuidV7Rule {
    fn type_name() -> &'static str {
        "UuidV7"
    }

    fn version() -> Version {
        Version::SortRand
    }

    fn version_name() -> &'static str {
        "v7"
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct NanoIdRule;

impl CheckedStringRule for NanoIdRule {
    fn check(value: String) -> Result<String, TypeError> {
        validate_nano_id(&value)?;
        Ok(value)
    }
}

impl FromStr for NanoId {
    type Err = TypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Self::new(value)
    }
}

impl fmt::Display for NanoId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl Serialize for NanoId {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(self.as_str())
    }
}

impl<'de> Deserialize<'de> for NanoId {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value = String::deserialize(deserializer)?;
        Self::new(value).map_err(de::Error::custom)
    }
}

fn nano_id_from_random(length: usize) -> Result<String, TypeError> {
    let mut random = |size| random_bytes("NanoId", size);
    nano_id_format(NANO_ALPHABET, length, &mut random)
}

fn nano_id_format<F>(alphabet: &[u8], length: usize, random: &mut F) -> Result<String, TypeError>
where
    F: FnMut(usize) -> Result<Vec<u8>, TypeError>,
{
    let mut id = String::with_capacity(length);
    let mask = alphabet.len().next_power_of_two() - 1;
    let step = nano_id_step(mask, length, alphabet.len());
    while id.len() < length {
        for byte in random(step)? {
            let index = byte as usize & mask;
            if let Some(symbol) = alphabet.get(index) {
                id.push(*symbol as char);
                if id.len() == length {
                    return Ok(id);
                }
            }
        }
    }
    Ok(id)
}

fn nano_id_step(mask: usize, length: usize, alphabet_len: usize) -> usize {
    let numerator = 16usize.saturating_mul(mask).saturating_mul(length);
    let denominator = 10usize.saturating_mul(alphabet_len);
    numerator.div_ceil(denominator).max(1)
}

fn random_bytes(type_name: &'static str, size: usize) -> Result<Vec<u8>, TypeError> {
    let mut bytes = vec![0; size];
    getrandom::fill(&mut bytes).map_err(|error| random_error(type_name, error))?;
    Ok(bytes)
}

fn validate_nano_id(value: &str) -> Result<(), TypeError> {
    if value.is_empty() {
        return Err(TypeError::empty("NanoId"));
    }
    if value.bytes().all(|byte| NANO_ALPHABET.contains(&byte)) {
        return Ok(());
    }
    let (index, character) = invalid_nano_symbol(value);
    Err(TypeError::new(
        "NanoId",
        format!("invalid NanoID character {character:?} at byte {index}"),
        "use URL-safe NanoID symbols: A-Z, a-z, 0-9, underscore, and dash",
    ))
}

fn invalid_nano_symbol(value: &str) -> (usize, char) {
    value
        .char_indices()
        .find(|(_, character)| !nano_symbol_valid(*character))
        .expect("invalid NanoID symbol must exist")
}

fn nano_symbol_valid(character: char) -> bool {
    character.is_ascii() && NANO_ALPHABET.contains(&(character as u8))
}

fn uuid_error(type_name: &'static str, error: uuid::Error) -> TypeError {
    TypeError::new(
        type_name,
        format!("invalid UUID: {error}"),
        "provide a UUID string like 018f9c92-7b86-7cc5-98c4-b3f8a2f0f9b1",
    )
}

fn random_error(type_name: &'static str, error: getrandom::Error) -> TypeError {
    TypeError::new(
        type_name,
        format!("random ID generation failed: {error}"),
        "check OS randomness availability and try again",
    )
}

#[cfg(test)]
mod tests;
