use serde::{Deserialize, Serialize};
use unicode_normalization::UnicodeNormalization;
use sha2::{Digest, Sha256};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NormalizationResult {
    Normalized(String),
    Invalid(String),
}

pub trait Normalizer: Send + Sync {
    fn normalize(&self, value: &str) -> NormalizationResult;
}

pub struct EmailNormalizer;
impl Normalizer for EmailNormalizer {
    fn normalize(&self, value: &str) -> NormalizationResult {
        let trimmed = value.trim().to_lowercase();
        let parts: Vec<&str> = trimmed.splitn(2, '@').collect();
        if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
            return NormalizationResult::Invalid("Invalid email format".to_string());
        }

        let (local, domain) = (parts[0], parts[1]);

        let normalized_local = if domain == "gmail.com" || domain == "googlemail.com" {
            let without_plus = local.split('+').next().unwrap_or(local);
            without_plus.replace('.', "")
        } else {
            local.to_string()
        };

        NormalizationResult::Normalized(format!("{}@{}", normalized_local, domain))
    }
}

pub struct PhoneNormalizer;
impl Normalizer for PhoneNormalizer {
    fn normalize(&self, value: &str) -> NormalizationResult {
        let trimmed = value.trim();
        let has_plus = trimmed.starts_with('+');
        let digits: String = trimmed.chars().filter(|c| c.is_ascii_digit()).collect();

        if digits.is_empty() {
            return NormalizationResult::Invalid("No digits found".to_string());
        }

        let e164 = if has_plus {
            format!("+{}", digits)
        } else if digits.len() == 10 {
            format!("+1{}", digits)
        } else if digits.len() >= 11 && digits.len() <= 15 {
            format!("+{}", digits)
        } else if digits.len() < 10 {
            return NormalizationResult::Invalid("Too few digits for E.164".to_string());
        } else {
            return NormalizationResult::Invalid("Too many digits for E.164".to_string());
        };

        let digit_part = &e164[1..];
        if digit_part.len() > 15 || digit_part.starts_with('0') {
            return NormalizationResult::Invalid("Invalid E.164 format".to_string());
        }

        NormalizationResult::Normalized(e164)
    }
}

pub struct NameNormalizer;
impl Normalizer for NameNormalizer {
    fn normalize(&self, value: &str) -> NormalizationResult {
        let nfc: String = value.nfc().collect();
        let lowered = nfc.to_lowercase();
        let normalized = lowered
            .split_whitespace()
            .collect::<Vec<_>>()
            .join(" ");
        NormalizationResult::Normalized(normalized)
    }
}

pub struct DomainNormalizer;
impl Normalizer for DomainNormalizer {
    fn normalize(&self, value: &str) -> NormalizationResult {
        let mut normalized = value.trim().to_lowercase();
        normalized = normalized
            .strip_prefix("https://")
            .or_else(|| normalized.strip_prefix("http://"))
            .unwrap_or(&normalized)
            .to_string();
        normalized = normalized.strip_prefix("www.").unwrap_or(&normalized).to_string();
        if let Some(pos) = normalized.find('/') {
            normalized = normalized[..pos].to_string();
        }
        NormalizationResult::Normalized(normalized)
    }
}

pub struct GenericNormalizer;
impl Normalizer for GenericNormalizer {
    fn normalize(&self, value: &str) -> NormalizationResult {
        NormalizationResult::Normalized(value.trim().to_lowercase())
    }
}

pub struct HashedNormalizer {
    pub inner: Box<dyn Normalizer>,
    pub salt: String,
}

impl Normalizer for HashedNormalizer {
    fn normalize(&self, value: &str) -> NormalizationResult {
        match self.inner.normalize(value) {
            NormalizationResult::Normalized(n) => {
                let mut hasher = Sha256::new();
                hasher.update(self.salt.as_bytes());
                hasher.update(n.as_bytes());
                let result = hasher.finalize();
                NormalizationResult::Normalized(hex::encode(result))
            }
            res => res,
        }
    }
}
