use regex::Regex;

/// Trait for sanitizing data models before API output
pub trait SanitizeOutput {
    fn sanitize_output(self) -> Self;
}

/// Strips all HTML tags from a string, leaving only plain text content
pub fn strip_html_tags(input: &str) -> String {
    clean_text_for_ios(input)
}

/// Comprehensive text cleaning for iOS compatibility
/// - Removes HTML tags
/// - Decodes ALL HTML entities
/// - Converts Windows line endings to Unix
/// - Trims excessive whitespace
/// - Normalizes multiple spaces/newlines
pub fn clean_text_for_ios(input: &str) -> String {
    if input.is_empty() {
        return String::new();
    }

    // Create regex to match HTML tags
    let html_tag_regex = Regex::new(r"<[^>]*>").unwrap();
    
    // Convert common line break tags to newlines before removing other tags
    let with_line_breaks = input
        .replace("<br/>", "\n")
        .replace("<br>", "\n")
        .replace("<br />", "\n")
        .replace("</p>", "\n")
        .replace("</div>", "\n")
        .replace("</li>", "\n");
    
    // Remove all remaining HTML tags
    let without_tags = html_tag_regex.replace_all(&with_line_breaks, "");
    
    // Decode ALL HTML entities (comprehensive list for iOS compatibility)
    let decoded = without_tags
        // Common entities
        .replace("&amp;", "&")
        .replace("&lt;", "<")
        .replace("&gt;", ">")
        .replace("&quot;", "\"")
        .replace("&#39;", "'")
        .replace("&apos;", "'")
        .replace("&nbsp;", " ")
        // Extended Latin entities (common in church content)
        .replace("&aelig;", "æ")
        .replace("&AElig;", "Æ")
        .replace("&agrave;", "à")
        .replace("&Agrave;", "À")
        .replace("&aacute;", "á")
        .replace("&Aacute;", "Á")
        .replace("&acirc;", "â")
        .replace("&Acirc;", "Â")
        .replace("&atilde;", "ã")
        .replace("&Atilde;", "Ã")
        .replace("&auml;", "ä")
        .replace("&Auml;", "Ä")
        .replace("&aring;", "å")
        .replace("&Aring;", "Å")
        .replace("&ccedil;", "ç")
        .replace("&Ccedil;", "Ç")
        .replace("&egrave;", "è")
        .replace("&Egrave;", "È")
        .replace("&eacute;", "é")
        .replace("&Eacute;", "É")
        .replace("&ecirc;", "ê")
        .replace("&Ecirc;", "Ê")
        .replace("&euml;", "ë")
        .replace("&Euml;", "Ë")
        .replace("&igrave;", "ì")
        .replace("&Igrave;", "Ì")
        .replace("&iacute;", "í")
        .replace("&Iacute;", "Í")
        .replace("&icirc;", "î")
        .replace("&Icirc;", "Î")
        .replace("&iuml;", "ï")
        .replace("&Iuml;", "Ï")
        .replace("&ntilde;", "ñ")
        .replace("&Ntilde;", "Ñ")
        .replace("&ograve;", "ò")
        .replace("&Ograve;", "Ò")
        .replace("&oacute;", "ó")
        .replace("&Oacute;", "Ó")
        .replace("&ocirc;", "ô")
        .replace("&Ocirc;", "Ô")
        .replace("&otilde;", "õ")
        .replace("&Otilde;", "Õ")
        .replace("&ouml;", "ö")
        .replace("&Ouml;", "Ö")
        .replace("&oslash;", "ø")
        .replace("&Oslash;", "Ø")
        .replace("&ugrave;", "ù")
        .replace("&Ugrave;", "Ù")
        .replace("&uacute;", "ú")
        .replace("&Uacute;", "Ú")
        .replace("&ucirc;", "û")
        .replace("&Ucirc;", "Û")
        .replace("&uuml;", "ü")
        .replace("&Uuml;", "Ü")
        .replace("&yacute;", "ý")
        .replace("&Yacute;", "Ý")
        .replace("&yuml;", "ÿ")
        // Special characters
        .replace("&copy;", "©")
        .replace("&reg;", "®")
        .replace("&trade;", "™")
        .replace("&sect;", "§")
        .replace("&para;", "¶")
        .replace("&middot;", "·")
        .replace("&hellip;", "…")
        .replace("&ndash;", "–")
        .replace("&mdash;", "—")
        .replace("&lsquo;", "'")
        .replace("&rsquo;", "'")
        .replace("&sbquo;", "‚")
        .replace("&ldquo;", "\"")
        .replace("&rdquo;", "\"")
        .replace("&bdquo;", "„")
        .replace("&dagger;", "†")
        .replace("&Dagger;", "‡")
        .replace("&bull;", "•")
        .replace("&lsaquo;", "‹")
        .replace("&rsaquo;", "›")
        .replace("&euro;", "€")
        // Numeric entities (common ones)
        .replace("&#8211;", "–")  // en dash
        .replace("&#8212;", "—")  // em dash
        .replace("&#8216;", "'")  // left single quote
        .replace("&#8217;", "'")  // right single quote
        .replace("&#8220;", "\"")  // left double quote
        .replace("&#8221;", "\"")  // right double quote
        .replace("&#8230;", "…")  // ellipsis
        .replace("&#160;", " ")   // non-breaking space
        .replace("&#8482;", "™"); // trademark

    // Convert Windows line endings to Unix
    let unix_lines = decoded.replace("\r\n", "\n").replace("\r", "\n");
    
    // Normalize whitespace but preserve intentional formatting
    let whitespace_regex = Regex::new(r"[ \t]+").unwrap();
    let normalized_spaces = whitespace_regex.replace_all(&unix_lines, " ");
    
    // Normalize excessive newlines (more than 2 consecutive newlines become 2)
    let newline_regex = Regex::new(r"\n{3,}").unwrap();
    let normalized_newlines = newline_regex.replace_all(&normalized_spaces, "\n\n");
    
    // Trim leading/trailing whitespace but preserve internal structure
    normalized_newlines.trim().to_string()
}


/// Helper function to sanitize an optional String field
pub fn sanitize_option_string(input: Option<String>) -> Option<String> {
    input.map(|s| strip_html_tags(&s))
}

/// Helper function to sanitize a String field
pub fn sanitize_string(input: String) -> String {
    strip_html_tags(&input)
}

// Implement SanitizeOutput for basic types that don't need sanitization
impl SanitizeOutput for () {
    fn sanitize_output(self) -> Self { self }
}

impl SanitizeOutput for String {
    fn sanitize_output(self) -> Self {
        strip_html_tags(&self)
    }
}

impl SanitizeOutput for i32 {
    fn sanitize_output(self) -> Self { self }
}

impl SanitizeOutput for i64 {
    fn sanitize_output(self) -> Self { self }
}

impl SanitizeOutput for bool {
    fn sanitize_output(self) -> Self { self }
}

impl SanitizeOutput for serde_json::Value {
    fn sanitize_output(self) -> Self {
        match self {
            serde_json::Value::String(s) => serde_json::Value::String(strip_html_tags(&s)),
            serde_json::Value::Object(mut map) => {
                for (_, value) in map.iter_mut() {
                    *value = std::mem::take(value).sanitize_output();
                }
                serde_json::Value::Object(map)
            },
            serde_json::Value::Array(arr) => {
                serde_json::Value::Array(
                    arr.into_iter().map(|v| v.sanitize_output()).collect()
                )
            },
            other => other,
        }
    }
}

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

    #[test]
    fn test_strip_html_tags() {
        assert_eq!(strip_html_tags("<p>Hello world</p>"), "Hello world");
        assert_eq!(strip_html_tags("<div><span>Test</span></div>"), "Test");
        assert_eq!(strip_html_tags("No HTML here"), "No HTML here");
        assert_eq!(strip_html_tags("<script>alert('xss')</script>"), "alert('xss')");
        assert_eq!(strip_html_tags(""), "");
    }

    #[test]
    fn test_html_entities() {
        assert_eq!(strip_html_tags("&amp;&lt;&gt;&quot;&#39;"), "&<>\"'");
        assert_eq!(strip_html_tags("Hello&nbsp;world"), "Hello world");
        assert_eq!(strip_html_tags("&nbsp;"), "");  // Single space gets trimmed
    }

    #[test]
    fn test_sanitize_text_with_length_limit() {
        assert_eq!(sanitize_text("<p>Hello world</p>", Some(5)), "Hello...");
        assert_eq!(sanitize_text("Short", Some(10)), "Short");
    }

    #[test]
    fn test_sanitize_optional_text() {
        assert_eq!(sanitize_optional_text(Some("<p>Hello</p>"), None), Some("Hello".to_string()));
        assert_eq!(sanitize_optional_text(Some("<p></p>"), None), None);
        assert_eq!(sanitize_optional_text(None, None), None);
    }

    #[test]
    fn test_sanitize_output_trait() {
        // Test String sanitization
        let dirty_string = "<p>Hello&nbsp;world&amp; more&lt;test&gt;</p>".to_string();
        let clean_string = dirty_string.sanitize_output();
        assert_eq!(clean_string, "Hello world& more<test>");

        // Test Vec sanitization
        let dirty_vec = vec![
            "<p>First&nbsp;item</p>".to_string(),
            "&amp;Second item&lt;".to_string(),
        ];
        let clean_vec = dirty_vec.sanitize_output();
        assert_eq!(clean_vec, vec!["First item".to_string(), "&Second item<".to_string()]);

        // Test Option sanitization
        let dirty_option = Some("<div>Test&nbsp;content</div>".to_string());
        let clean_option = dirty_option.sanitize_output();
        assert_eq!(clean_option, Some("Test content".to_string()));
    }

    #[test]
    fn test_comprehensive_ios_cleaning() {
        // Test HTML entities
        let html_entities = "&amp;&lt;&gt;&quot;&#39;&apos;&aelig;&copy;&trade;&nbsp;space";
        let cleaned = clean_text_for_ios(html_entities);
        assert_eq!(cleaned, "&<>\"''æ©™ space");

        // Test Windows line endings
        let windows_text = "Line 1\r\nLine 2\rLine 3\nLine 4";
        let cleaned = clean_text_for_ios(windows_text);
        assert_eq!(cleaned, "Line 1\nLine 2\nLine 3\nLine 4");

        // Test excessive whitespace
        let whitespace_text = "Too   many    spaces\t\tand\ttabs";
        let cleaned = clean_text_for_ios(whitespace_text);
        assert_eq!(cleaned, "Too many spaces and tabs");

        // Test excessive newlines
        let newline_text = "Para 1\n\n\n\n\nPara 2";
        let cleaned = clean_text_for_ios(newline_text);
        assert_eq!(cleaned, "Para 1\n\nPara 2");

        // Test bulletin-like content
        let bulletin_content = r#"<div>Divine&nbsp;Worship&nbsp;&amp;&nbsp;Service<br/>Time:&nbsp;11:00&nbsp;AM<br/>Speaker:&nbsp;Pastor&nbsp;John&nbsp;Doe</div>

<p>Scripture&nbsp;Reading:&nbsp;&ldquo;Matthew&nbsp;5:1-12&rdquo;&nbsp;&ndash;&nbsp;The&nbsp;Beatitudes</p>"#;
        let cleaned = clean_text_for_ios(bulletin_content);
        assert_eq!(cleaned, "Divine Worship & Service\nTime: 11:00 AM\nSpeaker: Pastor John Doe\n\nScripture Reading: \"Matthew 5:1-12\" – The Beatitudes");

        // Test extended Latin characters (for international names/places)
        let latin_text = "&aacute;&eacute;&iacute;&oacute;&uacute;&ntilde;&ccedil;";
        let cleaned = clean_text_for_ios(latin_text);
        assert_eq!(cleaned, "áéíóúñç");
    }
}