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)
    }
}

/// Nickname normalizer: applies NameNormalizer, then maps common nicknames
/// to their canonical form (e.g., "bob" → "robert", "liz" → "elizabeth").
/// This closes the gap with Splink's nickname resolution preprocessing.
pub struct NicknameNormalizer;
impl NicknameNormalizer {
    fn canonical(name: &str) -> &str {
        match name {
            "bob" | "rob" | "robbie" | "robby" | "bobby" => "robert",
            "bill" | "will" | "willy" | "willie" | "billy" | "liam" => "william",
            "dick" | "rick" | "rich" | "richie" | "ricky" => "richard",
            "jim" | "jimmy" | "jamie" => "james",
            "mike" | "mikey" | "mick" => "michael",
            "jen" | "jenny" => "jennifer",
            "liz" | "beth" | "betty" | "betsy" | "eliza" | "lizzy" => "elizabeth",
            "pat" | "patty" | "tricia" | "trish" => "patricia",
            "chris" => "christopher",
            "kate" | "kathy" | "katie" | "kat" | "kath" => "katherine",
            "ben" | "benny" => "benjamin",
            "nick" | "nicky" => "nicholas",
            "tom" | "tommy" => "thomas",
            "dan" | "danny" => "daniel",
            "dave" | "davy" => "david",
            "steve" | "stephen" => "steven",
            "joe" | "joey" => "joseph",
            "tony" => "anthony",
            "ed" | "eddie" | "ted" | "teddy" | "ned" => "edward",
            "sam" | "sammy" => "samuel",
            "matt" | "matty" => "matthew",
            "andy" | "drew" => "andrew",
            "alex" | "xander" => "alexander",
            "charlie" | "chuck" | "chaz" => "charles",
            "harry" | "hank" | "hal" => "henry",
            "jack" | "johnny" => "john",
            "larry" => "lawrence",
            "jerry" => "gerald",
            "terry" => "terrence",
            "ray" => "raymond",
            "al" => "alan",
            "meg" | "maggie" | "peggy" | "marge" => "margaret",
            "sue" | "susie" | "suzy" => "susan",
            "deb" | "debbie" | "debby" => "deborah",
            "barb" | "babs" => "barbara",
            "cathy" | "cath" => "catherine",
            "vicky" | "vicki" => "victoria",
            "mandy" => "amanda",
            "becky" => "rebecca",
            "cindy" => "cynthia",
            "sandy" => "sandra",
            "wendy" => "gwendolyn",
            "nate" => "nathan",
            "walt" => "walter",
            "fred" | "freddy" => "frederick",
            "frank" | "frankie" => "francis",
            "phil" => "philip",
            "ron" | "ronnie" => "ronald",
            "don" | "donnie" => "donald",
            "doug" => "douglas",
            "greg" => "gregory",
            "jeff" => "jeffrey",
            "ken" | "kenny" => "kenneth",
            "tim" | "timmy" => "timothy",
            "abe" => "abraham",
            "gus" => "augustus",
            "pete" => "peter",
            "gene" => "eugene",
            "ernie" => "ernest",
            "archie" => "archibald",
            "lenny" | "len" => "leonard",
            _ => name,
        }
    }
}
impl Normalizer for NicknameNormalizer {
    fn normalize(&self, value: &str) -> NormalizationResult {
        // First apply standard name normalization (NFC, lowercase, whitespace)
        let name_result = NameNormalizer.normalize(value);
        match name_result {
            NormalizationResult::Normalized(normalized) => {
                let canonical = Self::canonical(&normalized);
                NormalizationResult::Normalized(canonical.to_string())
            }
            other => other,
        }
    }
}

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,
        }
    }
}
