use super::super::Verdict;
use anyhow::{anyhow, Result};
const WEIGHTS: [u32; 8] = [5, 4, 3, 2, 7, 6, 5, 4];
fn parse_ch_uid(input: &str) -> String {
let clean_upper = input
.chars()
.filter(|c| !c.is_ascii_whitespace() && *c != '.' && *c != '-')
.collect::<String>()
.to_ascii_uppercase();
let after_prefix = clean_upper.strip_prefix("CHE").unwrap_or(&clean_upper);
let without_suffix = after_prefix
.strip_suffix("MWST")
.or_else(|| after_prefix.strip_suffix("IVA"))
.or_else(|| after_prefix.strip_suffix("TVA"))
.unwrap_or(after_prefix);
without_suffix.to_string()
}
fn compute_check(digits8: &[u32]) -> Option<u32> {
let sum: u32 = digits8.iter().zip(WEIGHTS.iter()).map(|(d, w)| d * w).sum();
let check_raw = 11 - (sum % 11);
if check_raw == 10 {
None } else if check_raw == 11 {
Some(0)
} else {
Some(check_raw)
}
}
pub fn verify_ch_vat(input: &str) -> Verdict {
let clean = parse_ch_uid(input);
if clean.len() != 9 {
return Verdict::Invalid {
reason: format!("expected 9 digits after stripping CHE prefix and suffix, got {}", clean.len()),
};
}
if !clean.chars().all(|c| c.is_ascii_digit()) {
return Verdict::Invalid {
reason: "non-digit input".into(),
};
}
let digits: Vec<u32> = clean.chars().map(|c| c.to_digit(10).unwrap()).collect();
match compute_check(&digits[..8]) {
None => Verdict::Invalid {
reason: "CH UID check_raw == 10: structurally invalid number".into(),
},
Some(check) => {
if check == digits[8] {
Verdict::Valid {
formatted: format!("CHE-{}.{}.{}", &clean[..3], &clean[3..6], &clean[6..]),
detected: "Swiss UID / IDE".into(),
comment: String::new(),
}
} else {
Verdict::Invalid {
reason: format!("CH UID check mismatch: expected {}, got {}", check, digits[8]),
}
}
}
}
}
pub fn create_ch_vat(input: &str, _raw: bool) -> Result<String> {
let clean = parse_ch_uid(input);
if clean.len() != 8 {
return Err(anyhow!("expected 8 digits (UID body without check digit), got {} (parsed: {:?})", clean.len(), clean));
}
if !clean.chars().all(|c| c.is_ascii_digit()) {
return Err(anyhow!("non-digit input"));
}
let digits: Vec<u32> = clean.chars().map(|c| c.to_digit(10).unwrap()).collect();
match compute_check(&digits) {
None => Err(anyhow!("this 8-digit body produces check_raw=10, which is structurally invalid")),
Some(check) => Ok(format!("CHE-{}.{}.{}{}", &clean[..3], &clean[3..6], &clean[6..], check)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ch_vat_reference_100155212() {
match verify_ch_vat("100155212") {
Verdict::Valid { formatted, detected, .. } => {
assert_eq!(formatted, "CHE-100.155.212");
assert!(detected.contains("Swiss"));
}
v => panic!("{:?}", v),
}
}
#[test]
fn ch_vat_reference_116281710_check_raw_11() {
match verify_ch_vat("116281710") {
Verdict::Valid { formatted, .. } => {
assert_eq!(formatted, "CHE-116.281.710");
}
v => panic!("{:?}", v),
}
}
#[test]
fn ch_vat_reference_107787577() {
match verify_ch_vat("107787577") {
Verdict::Valid { formatted, .. } => {
assert_eq!(formatted, "CHE-107.787.577");
}
v => panic!("{:?}", v),
}
}
#[test]
fn ch_vat_accepts_che_prefix_and_dots() {
match verify_ch_vat("CHE-100.155.212") {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn ch_vat_accepts_mwst_suffix() {
match verify_ch_vat("CHE-100.155.212 MWST") {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn ch_vat_accepts_tva_suffix() {
match verify_ch_vat("CHE-100.155.212 TVA") {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn ch_vat_accepts_iva_suffix() {
match verify_ch_vat("CHE-100.155.212 IVA") {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn ch_vat_rejects_bad_check() {
match verify_ch_vat("100155213") {
Verdict::Invalid { reason } => assert!(reason.contains("check mismatch")),
v => panic!("{:?}", v),
}
}
#[test]
fn ch_vat_rejects_bad_length() {
match verify_ch_vat("10015521") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn ch_vat_round_trip() {
let full = create_ch_vat("10015521", false).unwrap();
assert_eq!(full, "CHE-100.155.212");
match verify_ch_vat(&full) {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
}