use crate::types::CleanConfig;
use url::Url;

pub fn clean_whitespace(input: &str) -> String {
  input
    .chars()
    .filter(|&c| c != '\u{200b}' && c != '\x00')
    .collect::<String>()
    .trim()
    .to_string()
}

pub fn fix_typography(input: &str) -> String {
  let mut result = String::with_capacity(input.len());

  for c in input.chars() {
    match c {
      '\u{201c}' | '\u{201d}' => result.push('"'),
      '\u{2018}' | '\u{2019}' => result.push('\''),
      '\u{2014}' => result.push_str("--"),
      '\u{2013}' => result.push('-'),
      _ => result.push(c),
    }
  }

  result
}

fn is_markdown_line(line: &str) -> bool {
  let trimmed = line.trim_start();

  trimmed.starts_with("- ")
    || trimmed.starts_with("* ")
    || trimmed.starts_with("#")
    || (trimmed.len() > 2
      && trimmed.chars().next().unwrap().is_ascii_digit()
      && trimmed.contains(". "))
}

pub fn normalize_newlines(input: &str) -> String {
  let normalized = input.replace("\r\n", "\n");
  let without_hyphens = normalized.replace("-\n", "");
  let lines: Vec<&str> = without_hyphens.lines().collect();

  let mut result = String::new();

  for i in 0..lines.len() {
    let current = lines[i];
    result.push_str(current);

    if i < lines.len() - 1 {
      let next = lines[i + 1];

      if current.is_empty()
        || next.is_empty()
        || is_markdown_line(next)
        || is_markdown_line(current)
      {
        result.push('\n');
      } else {
        result.push(' ');
      }
    }
  }

  result
}

pub fn scrub_url_trackers(input: &str, custom_params: &[String]) -> String {
  let mut result = String::new();
  let mut current_idx = 0;
  let chars: Vec<char> = input.chars().collect();

  while current_idx < chars.len() {
    if (current_idx + 7 <= chars.len()
      && chars[current_idx..current_idx + 7] == ['h', 't', 't', 'p', ':', '/', '/'])
      || (current_idx + 8 <= chars.len()
        && chars[current_idx..current_idx + 8] == ['h', 't', 't', 'p', 's', ':', '/', '/'])
    {
      let start = current_idx;

      while current_idx < chars.len() && !chars[current_idx].is_whitespace() {
        current_idx += 1;
      }

      let url_str: String = chars[start..current_idx].iter().collect();

      if let Ok(mut url) = Url::parse(&url_str) {
        if url.query().is_some() {
          let query_pairs: Vec<(String, String)> = url
            .query_pairs()
            .map(|(k, v)| (k.into_owned(), v.into_owned()))
            .filter(|(k, _)| {
              let kl = k.to_lowercase();
              !kl.starts_with("utm_")
                && kl != "gclid"
                && kl != "fbclid"
                && kl != "si"
                && !custom_params.iter().any(|p| p.to_lowercase() == kl)
            })
            .collect();

          url.set_query(None);

          if !query_pairs.is_empty() {
            let mut serializer = url.query_pairs_mut();

            for (k, v) in query_pairs {
              serializer.append_pair(&k, &v);
            }
          }
        }

        result.push_str(url.as_str());
      } else {
        result.push_str(&url_str);
      }
    } else {
      result.push(chars[current_idx]);
      current_idx += 1;
    }
  }

  result
}

pub fn sanitize_text(input: &str, config: &CleanConfig) -> String {
  let mut text = clean_whitespace(input);

  if config.fix_quotes {
    text = fix_typography(&text);
  }

  if config.fix_newlines {
    text = normalize_newlines(&text);
  }

  if config.strip_utms {
    text = scrub_url_trackers(&text, &config.custom_params);
  }

  text
}

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

  #[test]
  fn test_whitespace() {
    let input = "\u{200b}  hello\x00world\t \n ";
    let result = clean_whitespace(input);
    assert_eq!(result, "helloworld");
  }

  #[test]
  fn test_typography() {
    let input = "\u{201c}hello\u{201d} \u{2018}world\u{2019}\u{2014}dash\u{2013}en";
    let result = fix_typography(input);
    assert_eq!(result, "\"hello\" 'world'--dash-en");
  }

  #[test]
  fn test_newlines() {
    let input = "inno-\nvation is\ngreat.\n\nParagraph 2.\n- Item 1\n- Item 2";
    let result = normalize_newlines(input);
    let expected = "innovation is great.\n\nParagraph 2.\n- Item 1\n- Item 2";
    assert_eq!(result, expected);
  }

  #[test]
  fn test_url_scrubber() {
    let input =
      "Check https://example.com/path?utm_source=twitter&id=1024&gclid=123#frag and simple text.";
    let result = scrub_url_trackers(input, &[]);
    let expected = "Check https://example.com/path?id=1024#frag and simple text.";
    assert_eq!(result, expected);
  }

  #[test]
  fn test_url_scrubber_custom_params() {
    let input = "https://example.com/page?ref=home&tracking_id=abc&id=99";
    let result = scrub_url_trackers(input, &["ref".to_string(), "tracking_id".to_string()]);
    let expected = "https://example.com/page?id=99";
    assert_eq!(result, expected);
  }

  #[test]
  fn test_url_scrubber_custom_params_only() {
    let input = "https://example.com/page?campaign=summer&fbclid=xyz&token=secret";
    let result = scrub_url_trackers(input, &["campaign".to_string()]);
    let expected = "https://example.com/page?token=secret";
    assert_eq!(result, expected);
  }

  #[test]
  fn test_url_scrubber_custom_params_case_insensitive() {
    let input = "https://example.com/page?Ref=home&REF=other&id=1";
    let result = scrub_url_trackers(input, &["ref".to_string()]);
    let expected = "https://example.com/page?id=1";
    assert_eq!(result, expected);
  }

  #[test]
  fn test_master_pipeline() {
    let config = CleanConfig {
      strip_utms: true,
      fix_quotes: true,
      fix_newlines: true,
      ..CleanConfig::default()
    };

    let input = "\u{201c}inno-\nvation\u{201d} on https://example.com?utm_source=test&id=123";
    let result = sanitize_text(input, &config);
    let expected = "\"innovation\" on https://example.com/?id=123";
    assert_eq!(result, expected);
  }
}
