use super::super::{sanitize, Verdict};
use anyhow::{anyhow, Result};
const ICO_WEIGHTS: [u64; 7] = [8, 7, 6, 5, 4, 3, 2];
fn ico_check(body: &[u32]) -> u32 {
let sum: u64 = body.iter().zip(ICO_WEIGHTS.iter()).map(|(d, w)| *d as u64 * w).sum();
let modulo = (sum % 11) as u32;
match modulo {
0 => 1,
1 => 0,
10 => 1,
m => 11 - m,
}
}
pub fn verify_cz_legal(input: &str) -> Verdict {
let clean = sanitize(input, false);
if clean.len() != 8 {
return Verdict::Invalid {
reason: format!("Czech IČO requires 8 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.chars().map(|c| c.to_digit(10).unwrap()).collect();
let expected = ico_check(&digits[..7]);
let got = digits[7];
if expected == got {
Verdict::Valid {
formatted: clean,
detected: "Czech IČO (legal entity)".into(),
comment: String::new(),
}
} else {
Verdict::Invalid {
reason: format!("IČO check mismatch: expected {}, got {}", expected, got),
}
}
}
pub fn create_cz_legal(input: &str, _raw: bool) -> Result<String> {
let clean = sanitize(input, false);
if clean.len() != 7 {
return Err(anyhow!(
"expected 7 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 check = ico_check(&digits);
Ok(format!("{}{}", clean, check))
}
pub fn verify_cz_person(input: &str) -> Verdict {
let clean = sanitize(input, false);
match clean.len() {
9 => {
if !clean.chars().all(|c| c.is_ascii_digit()) {
return Verdict::Invalid { reason: "non-digit input".into() };
}
Verdict::Valid {
formatted: clean,
detected: "Czech rodné číslo (pre-1954, no check digit)".into(),
comment: String::new(),
}
}
10 => {
if !clean.chars().all(|c| c.is_ascii_digit()) {
return Verdict::Invalid { reason: "non-digit input".into() };
}
let value: u64 = clean.parse().map_err(|_| ()).unwrap_or(u64::MAX);
if value % 11 == 0 {
Verdict::Valid {
formatted: clean,
detected: "Czech rodné číslo (personal code)".into(),
comment: String::new(),
}
} else {
Verdict::Invalid {
reason: format!(
"rodné číslo check failed: {} is not divisible by 11",
clean
),
}
}
}
n => Verdict::Invalid {
reason: format!("Czech rodné číslo requires 9 or 10 digits, got {}", n),
},
}
}
pub fn create_cz_person(input: &str, _raw: bool) -> Result<String> {
let clean = sanitize(input, false);
if clean.len() != 9 {
return Err(anyhow!(
"expected 9 digits (body without check digit), got {}",
clean.len()
));
}
if !clean.chars().all(|c| c.is_ascii_digit()) {
return Err(anyhow!("non-digit input"));
}
let body: u64 = clean.parse()?;
let base = (body * 10) % 11;
let needed = (11 - base) % 11;
if needed <= 9 {
Ok(format!("{}{}", clean, needed))
} else {
Err(anyhow!(
"no single check digit (0-9) makes the 10-digit number divisible by 11 for body {}",
clean
))
}
}
pub fn verify_cz_vat(input: &str) -> Verdict {
let clean = match super::strip_vat_prefix(input, "CZ") {
Ok(body) => body,
Err(v) => return v,
};
match clean.len() {
8 => verify_cz_legal(&clean),
9 | 10 => verify_cz_person(&clean),
n => Verdict::Invalid {
reason: format!(
"Czech VAT: expected 8 digits (IČO) or 9-10 digits (rodné číslo), got {}",
n
),
},
}
}
pub fn create_cz_vat(_input: &str, _raw: bool) -> Result<String> {
Err(anyhow!(
"cz-vat auto-detect cannot create; use cz-legal (IČO) or cz-person (rodné číslo)"
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cz_legal_valid_46505334() {
match verify_cz_legal("46505334") {
Verdict::Valid { detected, .. } => assert_eq!(detected, "Czech IČO (legal entity)"),
v => panic!("{:?}", v),
}
}
#[test]
fn cz_legal_rejects_bad_check() {
match verify_cz_legal("46505335") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_legal_rejects_wrong_length() {
match verify_cz_legal("1234567") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_legal_round_trip() {
let body = "4650533";
let full = create_cz_legal(body, false).unwrap();
assert_eq!(full, "46505334");
match verify_cz_legal(&full) {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_legal_mod0_gives_check1() {
let full = create_cz_legal("1000010", false).unwrap();
assert_eq!(&full[7..], "1");
match verify_cz_legal(&full) {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_legal_mod1_gives_check0() {
let full = create_cz_legal("1000100", false).unwrap();
assert_eq!(&full[7..], "0");
match verify_cz_legal(&full) {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_person_valid_10digit_7301011234() {
match verify_cz_person("7301011234") {
Verdict::Valid { detected, .. } => {
assert_eq!(detected, "Czech rodné číslo (personal code)")
}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_person_valid_9digit_pre1954() {
match verify_cz_person("530101001") {
Verdict::Valid { detected, .. } => {
assert_eq!(detected, "Czech rodné číslo (pre-1954, no check digit)")
}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_person_rejects_not_divisible_by_11() {
match verify_cz_person("7301011235") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_person_rejects_wrong_length() {
match verify_cz_person("12345678") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_person_round_trip_10digit() {
let body = "730101123";
let full = create_cz_person(body, false).unwrap();
assert_eq!(full, "7301011234");
match verify_cz_person(&full) {
Verdict::Valid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_vat_autodetect_8digit_is_ico() {
match verify_cz_vat("46505334") {
Verdict::Valid { detected, .. } => assert_eq!(detected, "Czech IČO (legal entity)"),
v => panic!("{:?}", v),
}
}
#[test]
fn cz_vat_autodetect_10digit_is_person() {
match verify_cz_vat("7301011234") {
Verdict::Valid { detected, .. } => {
assert_eq!(detected, "Czech rodné číslo (personal code)")
}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_vat_autodetect_9digit_is_pre1954() {
match verify_cz_vat("530101001") {
Verdict::Valid { detected, .. } => {
assert_eq!(detected, "Czech rodné číslo (pre-1954, no check digit)")
}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_vat_rejects_wrong_length() {
match verify_cz_vat("12345678901") {
Verdict::Invalid { .. } => {}
v => panic!("{:?}", v),
}
}
#[test]
fn cz_vat_create_returns_err() {
assert!(create_cz_vat("anything", false).is_err());
}
}