use super::super::{sanitize, Verdict};
use anyhow::{anyhow, Result};
const WEIGHTS: [u32; 9] = [8, 7, 6, 5, 4, 3, 2, 10, 1];
fn weighted_sum(digits: &[u32]) -> u32 {
digits.iter().zip(WEIGHTS.iter()).map(|(d, w)| d * w).sum()
}
pub fn verify_uk_vat(input: &str) -> Verdict {
let clean = match super::strip_vat_prefix(input, "UK") {
Ok(body) => body,
Err(v) => return v,
};
if clean.len() != 9 && clean.len() != 12 {
return Verdict::Invalid {
reason: format!("expected 9 or 12 digits, 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[..9].chars().map(|c| c.to_digit(10).unwrap()).collect();
let sum = weighted_sum(&digits);
let suffix = if clean.len() == 12 {
format!(" (branch {})", &clean[9..])
} else {
String::new()
};
if sum % 97 == 0 {
return Verdict::Valid {
formatted: format!("UK{}", clean),
detected: "UK VAT (classic mod-97)".into(),
comment: suffix,
};
}
if (sum + 55) % 97 == 0 {
return Verdict::Valid {
formatted: format!("UK{}", clean),
detected: "UK VAT (97-55)".into(),
comment: suffix,
};
}
Verdict::Invalid {
reason: format!(
"UK VAT check failed: sum={}, sum%97={} (algo A wants 0), (sum+55)%97={} (algo B wants 0)",
sum,
sum % 97,
(sum + 55) % 97
),
}
}
pub fn create_uk_vat(input: &str, _raw: bool) -> Result<String> {
let clean = sanitize(input, false);
if clean.len() != 8 {
return Err(anyhow!("expected 8 digits (body without check digit), got {}", clean.len()));
}
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();
let partial: u32 = digits.iter().zip(WEIGHTS[..8].iter()).map(|(d, w)| d * w).sum();
for cd in 0u32..=9 {
if (partial + cd) % 97 == 0 {
return Ok(format!("UK{}{}", clean, cd));
}
}
Err(anyhow!("no single check digit 0-9 satisfies algo A for this body (try a different 8-digit body)"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uk_vat_algo_a_333289454() {
match verify_uk_vat("333289454") {
Verdict::Valid { detected, .. } => {
assert!(detected.contains("classic"), "got detected={:?}", detected);
}
v => panic!("{:?}", v),
}
}
#[test]
fn uk_vat_algo_b_123456727() {
match verify_uk_vat("123456727") {
Verdict::Valid { detected, .. } => {
assert!(detected.contains("97-55"), "got detected={:?}", detected);
}
v => panic!("{:?}", v),
}
}
#[test]
fn uk_vat_accepts_uk_prefix() {
match verify_uk_vat("UK333289454") {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn uk_vat_rejects_wrong_length() {
match verify_uk_vat("33328945") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn uk_vat_rejects_bad_check() {
match verify_uk_vat("333289453") {
Verdict::Invalid { reason } => assert!(reason.contains("check failed")),
v => panic!("{:?}", v),
}
}
#[test]
fn uk_vat_12_digit_branch() {
match verify_uk_vat("333289454001") {
Verdict::Valid { comment, formatted, .. } => {
assert!(comment.contains("001"), "comment={:?}", comment);
assert_eq!(formatted, "UK333289454001");
}
v => panic!("{:?}", v),
}
}
#[test]
fn uk_vat_round_trip() {
let full = create_uk_vat("33328945", false).unwrap();
assert_eq!(full, "UK333289454");
match verify_uk_vat(&full) {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
}