use std::fmt;

pub const MAX_EMAIL_LENGTH: usize = 254;
pub const MAX_LOCAL_PART_LENGTH: usize = 64;
pub const MAX_DOMAIN_LENGTH: usize = 253;
pub const MAX_DOMAIN_LABEL_LENGTH: usize = 63;
const EMAIL_LOCAL_SPECIAL_CHARS: &str = ".!#$%&'*+/=?^_`{|}~-";

pub const MIN_HANDLE_LENGTH: usize = 3;
pub const MAX_HANDLE_LENGTH: usize = 253;
pub const MAX_SERVICE_HANDLE_LOCAL_PART: usize = 18;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EmailValidationError {
    Empty,
    TooLong,
    MissingAtSign,
    EmptyLocalPart,
    LocalPartTooLong,
    InvalidLocalPart,
    EmptyDomain,
    DomainTooLong,
    MissingDomainDot,
    InvalidDomainLabel,
}

impl fmt::Display for EmailValidationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Empty => write!(f, "Email cannot be empty"),
            Self::TooLong => write!(
                f,
                "Email exceeds maximum length of {} characters",
                MAX_EMAIL_LENGTH
            ),
            Self::MissingAtSign => write!(f, "Email must contain @"),
            Self::EmptyLocalPart => write!(f, "Email local part cannot be empty"),
            Self::LocalPartTooLong => write!(f, "Email local part exceeds maximum length"),
            Self::InvalidLocalPart => write!(f, "Email local part contains invalid characters"),
            Self::EmptyDomain => write!(f, "Email domain cannot be empty"),
            Self::DomainTooLong => write!(f, "Email domain exceeds maximum length"),
            Self::MissingDomainDot => write!(f, "Email domain must contain a dot"),
            Self::InvalidDomainLabel => write!(f, "Email domain contains invalid label"),
        }
    }
}

impl std::error::Error for EmailValidationError {}

fn validate_email_detailed(email: &str) -> Result<(), EmailValidationError> {
    if email.is_empty() {
        return Err(EmailValidationError::Empty);
    }
    if email.len() > MAX_EMAIL_LENGTH {
        return Err(EmailValidationError::TooLong);
    }
    let parts: Vec<&str> = email.rsplitn(2, '@').collect();
    if parts.len() != 2 {
        return Err(EmailValidationError::MissingAtSign);
    }
    let domain = parts[0];
    let local = parts[1];
    if local.is_empty() {
        return Err(EmailValidationError::EmptyLocalPart);
    }
    if local.len() > MAX_LOCAL_PART_LENGTH {
        return Err(EmailValidationError::LocalPartTooLong);
    }
    if local.starts_with('.') || local.ends_with('.') || local.contains("..") {
        return Err(EmailValidationError::InvalidLocalPart);
    }
    if !local
        .chars()
        .all(|c| c.is_ascii_alphanumeric() || EMAIL_LOCAL_SPECIAL_CHARS.contains(c))
    {
        return Err(EmailValidationError::InvalidLocalPart);
    }
    if domain.is_empty() {
        return Err(EmailValidationError::EmptyDomain);
    }
    if domain.len() > MAX_DOMAIN_LENGTH {
        return Err(EmailValidationError::DomainTooLong);
    }
    if !domain.contains('.') {
        return Err(EmailValidationError::MissingDomainDot);
    }
    if !domain.split('.').all(|label| {
        !label.is_empty()
            && label.len() <= MAX_DOMAIN_LABEL_LENGTH
            && !label.starts_with('-')
            && !label.ends_with('-')
            && label.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
    }) {
        return Err(EmailValidationError::InvalidDomainLabel);
    }
    Ok(())
}

#[derive(Debug, PartialEq)]
pub enum HandleValidationError {
    Empty,
    TooShort,
    TooLong,
    InvalidCharacters,
    StartsWithInvalidChar,
    EndsWithInvalidChar,
    ContainsSpaces,
    BannedWord,
    Reserved,
}

impl std::fmt::Display for HandleValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Empty => write!(f, "Handle cannot be empty"),
            Self::TooShort => write!(
                f,
                "Handle must be at least {} characters",
                MIN_HANDLE_LENGTH
            ),
            Self::TooLong => write!(
                f,
                "Handle exceeds maximum length of {} characters",
                MAX_SERVICE_HANDLE_LOCAL_PART
            ),
            Self::InvalidCharacters => write!(
                f,
                "Handle contains invalid characters. Only alphanumeric characters and hyphens are allowed"
            ),
            Self::StartsWithInvalidChar => {
                write!(f, "Handle cannot start with a hyphen")
            }
            Self::EndsWithInvalidChar => write!(f, "Handle cannot end with a hyphen"),
            Self::ContainsSpaces => write!(f, "Handle cannot contain spaces"),
            Self::BannedWord => write!(f, "Inappropriate language in handle"),
            Self::Reserved => write!(f, "Reserved handle"),
        }
    }
}

impl std::error::Error for HandleValidationError {}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReservedHandlePolicy {
    Allow,
    Reject,
}

pub fn validate_full_domain_handle(handle: &str) -> Result<String, HandleValidationError> {
    let handle = handle.trim();

    if handle.is_empty() {
        return Err(HandleValidationError::Empty);
    }

    if handle.contains(' ') || handle.contains('\t') || handle.contains('\n') {
        return Err(HandleValidationError::ContainsSpaces);
    }

    if handle.len() > MAX_HANDLE_LENGTH {
        return Err(HandleValidationError::TooLong);
    }

    if handle
        .chars()
        .any(|c| !c.is_ascii_alphanumeric() && c != '.' && c != '-')
    {
        return Err(HandleValidationError::InvalidCharacters);
    }

    if !handle.contains('.') {
        return Err(HandleValidationError::InvalidCharacters);
    }

    let labels: Vec<&str> = handle.split('.').collect();
    let has_invalid_label = labels.iter().any(|label| {
        label.is_empty()
            || label.len() > MAX_DOMAIN_LABEL_LENGTH
            || label.starts_with('-')
            || label.ends_with('-')
    });
    if has_invalid_label {
        return Err(HandleValidationError::InvalidCharacters);
    }

    let handle_lower = handle.to_lowercase();

    if crate::moderation::has_explicit_slur(&handle_lower) {
        return Err(HandleValidationError::BannedWord);
    }

    Ok(handle_lower)
}

pub fn validate_short_handle(handle: &str) -> Result<String, HandleValidationError> {
    validate_service_handle(handle, ReservedHandlePolicy::Reject)
}

pub fn resolve_handle_input(input: &str) -> Result<String, HandleValidationError> {
    let available_domains = tranquil_config::get().server.available_user_domain_list();
    let matched_domain = available_domains
        .iter()
        .filter(|d| input.ends_with(&format!(".{}", d)))
        .max_by_key(|d| d.len());

    if !input.contains('.') || matched_domain.is_some() {
        let handle_to_validate = match matched_domain {
            Some(domain) => input.strip_suffix(&format!(".{}", domain)).unwrap_or(input),
            None => input,
        };
        let validated = validate_short_handle(handle_to_validate)?;
        Ok(format!(
            "{}.{}",
            validated,
            matched_domain.unwrap_or(&available_domains[0])
        ))
    } else {
        validate_full_domain_handle(input)
    }
}

pub fn validate_service_handle(
    handle: &str,
    reserved_policy: ReservedHandlePolicy,
) -> Result<String, HandleValidationError> {
    let handle = handle.trim();

    if handle.is_empty() {
        return Err(HandleValidationError::Empty);
    }

    if handle.contains(' ') || handle.contains('\t') || handle.contains('\n') {
        return Err(HandleValidationError::ContainsSpaces);
    }

    if handle.len() < MIN_HANDLE_LENGTH {
        return Err(HandleValidationError::TooShort);
    }

    if handle.len() > MAX_SERVICE_HANDLE_LOCAL_PART {
        return Err(HandleValidationError::TooLong);
    }

    if let Some(first_char) = handle.chars().next()
        && first_char == '-'
    {
        return Err(HandleValidationError::StartsWithInvalidChar);
    }

    if let Some(last_char) = handle.chars().last()
        && last_char == '-'
    {
        return Err(HandleValidationError::EndsWithInvalidChar);
    }

    if !handle
        .chars()
        .all(|c| c.is_ascii_alphanumeric() || c == '-')
    {
        return Err(HandleValidationError::InvalidCharacters);
    }

    if crate::moderation::has_explicit_slur(handle) {
        return Err(HandleValidationError::BannedWord);
    }

    if reserved_policy == ReservedHandlePolicy::Reject
        && crate::handle::reserved::is_reserved_subdomain(handle)
    {
        return Err(HandleValidationError::Reserved);
    }

    Ok(handle.to_lowercase())
}

pub fn is_valid_email(email: &str) -> bool {
    validate_email_detailed(email.trim()).is_ok()
}

pub fn is_valid_telegram_username(username: &str) -> bool {
    let clean = username.strip_prefix('@').unwrap_or(username);
    (5..=32).contains(&clean.len()) && clean.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}

pub fn is_valid_discord_username(username: &str) -> bool {
    (2..=32).contains(&username.len())
        && username
            .chars()
            .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '.')
        && !username.starts_with('.')
        && !username.ends_with('.')
        && !username.contains("..")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_valid_handles() {
        assert_eq!(validate_short_handle("alice"), Ok("alice".to_string()));
        assert_eq!(validate_short_handle("bob123"), Ok("bob123".to_string()));
        assert_eq!(
            validate_short_handle("user-name"),
            Ok("user-name".to_string())
        );
        assert_eq!(
            validate_short_handle("UPPERCASE"),
            Ok("uppercase".to_string())
        );
        assert_eq!(
            validate_short_handle("MixedCase123"),
            Ok("mixedcase123".to_string())
        );
        assert_eq!(validate_short_handle("abc"), Ok("abc".to_string()));
    }

    #[test]
    fn test_invalid_handles() {
        assert_eq!(validate_short_handle(""), Err(HandleValidationError::Empty));
        assert_eq!(
            validate_short_handle("   "),
            Err(HandleValidationError::Empty)
        );
        assert_eq!(
            validate_short_handle("ab"),
            Err(HandleValidationError::TooShort)
        );
        assert_eq!(
            validate_short_handle("a"),
            Err(HandleValidationError::TooShort)
        );
        assert_eq!(
            validate_short_handle("test spaces"),
            Err(HandleValidationError::ContainsSpaces)
        );
        assert_eq!(
            validate_short_handle("test\ttab"),
            Err(HandleValidationError::ContainsSpaces)
        );
        assert_eq!(
            validate_short_handle("-starts"),
            Err(HandleValidationError::StartsWithInvalidChar)
        );
        assert_eq!(
            validate_short_handle("_starts"),
            Err(HandleValidationError::InvalidCharacters)
        );
        assert_eq!(
            validate_short_handle("ends-"),
            Err(HandleValidationError::EndsWithInvalidChar)
        );
        assert_eq!(
            validate_short_handle("ends_"),
            Err(HandleValidationError::InvalidCharacters)
        );
        assert_eq!(
            validate_short_handle("user_name"),
            Err(HandleValidationError::InvalidCharacters)
        );
        assert_eq!(
            validate_short_handle("test@user"),
            Err(HandleValidationError::InvalidCharacters)
        );
        assert_eq!(
            validate_short_handle("test!user"),
            Err(HandleValidationError::InvalidCharacters)
        );
        assert_eq!(
            validate_short_handle("test.user"),
            Err(HandleValidationError::InvalidCharacters)
        );
    }

    #[test]
    fn test_handle_trimming() {
        assert_eq!(validate_short_handle("  alice  "), Ok("alice".to_string()));
    }

    #[test]
    fn test_handle_max_length() {
        assert_eq!(
            validate_short_handle("exactly18charslol"),
            Ok("exactly18charslol".to_string())
        );
        assert_eq!(
            validate_short_handle("exactly18charslol1"),
            Ok("exactly18charslol1".to_string())
        );
        assert_eq!(
            validate_short_handle("exactly19characters"),
            Err(HandleValidationError::TooLong)
        );
        assert_eq!(
            validate_short_handle("waytoolongusername123456789"),
            Err(HandleValidationError::TooLong)
        );
    }

    #[test]
    fn test_reserved_subdomains() {
        assert_eq!(
            validate_short_handle("admin"),
            Err(HandleValidationError::Reserved)
        );
        assert_eq!(
            validate_short_handle("api"),
            Err(HandleValidationError::Reserved)
        );
        assert_eq!(
            validate_short_handle("bsky"),
            Err(HandleValidationError::Reserved)
        );
        assert_eq!(
            validate_short_handle("barackobama"),
            Err(HandleValidationError::Reserved)
        );
        assert_eq!(
            validate_short_handle("ADMIN"),
            Err(HandleValidationError::Reserved)
        );
        assert_eq!(validate_short_handle("alice"), Ok("alice".to_string()));
        assert_eq!(
            validate_short_handle("notreserved"),
            Ok("notreserved".to_string())
        );
    }

    #[test]
    fn test_allow_reserved() {
        assert_eq!(
            validate_service_handle("admin", ReservedHandlePolicy::Allow),
            Ok("admin".to_string())
        );
        assert_eq!(
            validate_service_handle("api", ReservedHandlePolicy::Allow),
            Ok("api".to_string())
        );
        assert_eq!(
            validate_service_handle("admin", ReservedHandlePolicy::Reject),
            Err(HandleValidationError::Reserved)
        );
    }

    #[test]
    fn test_valid_emails() {
        assert!(is_valid_email("user@example.com"));
        assert!(is_valid_email("user.name@example.com"));
        assert!(is_valid_email("user+tag@example.com"));
        assert!(is_valid_email("user@sub.example.com"));
        assert!(is_valid_email("USER@EXAMPLE.COM"));
        assert!(is_valid_email("user123@example123.com"));
        assert!(is_valid_email("a@b.co"));
    }
    #[test]
    fn test_invalid_emails() {
        assert!(!is_valid_email(""));
        assert!(!is_valid_email("user"));
        assert!(!is_valid_email("user@"));
        assert!(!is_valid_email("@example.com"));
        assert!(!is_valid_email("user@example"));
        assert!(!is_valid_email("user@@example.com"));
        assert!(!is_valid_email("user@.example.com"));
        assert!(!is_valid_email("user@example..com"));
        assert!(!is_valid_email(".user@example.com"));
        assert!(!is_valid_email("user.@example.com"));
        assert!(!is_valid_email("user..name@example.com"));
        assert!(!is_valid_email("user@-example.com"));
        assert!(!is_valid_email("user@example-.com"));
    }
    #[test]
    fn test_trimmed_whitespace() {
        assert!(is_valid_email("  user@example.com  "));
    }

    #[test]
    fn test_valid_discord_usernames() {
        assert!(is_valid_discord_username("ab"));
        assert!(is_valid_discord_username("alice"));
        assert!(is_valid_discord_username("user_name"));
        assert!(is_valid_discord_username("user.name"));
        assert!(is_valid_discord_username("user123"));
        assert!(is_valid_discord_username("a_b.c_d"));
        assert!(is_valid_discord_username(
            "12345678901234567890123456789012"
        ));
    }

    #[test]
    fn test_invalid_discord_usernames() {
        assert!(!is_valid_discord_username(""));
        assert!(!is_valid_discord_username("a"));
        assert!(!is_valid_discord_username("Alice"));
        assert!(!is_valid_discord_username("ALICE"));
        assert!(!is_valid_discord_username("user-name"));
        assert!(!is_valid_discord_username(".username"));
        assert!(!is_valid_discord_username("username."));
        assert!(!is_valid_discord_username("user..name"));
        assert!(!is_valid_discord_username("user name"));
        assert!(!is_valid_discord_username(
            "123456789012345678901234567890123"
        ));
    }
}
