package de.assecutor.pdftool.invoice;

import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;

/**
 * Prüft Umsatzsteuer-Identifikationsnummern (USt-IdNr.).
 *
 * Geprüft werden das länderspezifische Format aller EU-Mitgliedsstaaten
 * (plus XI für Nordirland) sowie bei deutschen IdNrn. zusätzlich die
 * Prüfziffer nach ISO 7064, MOD 11,10 (§ 27a UStG).
 */
public final class VatIdValidator {

    /** Format des nationalen Teils (ohne Länderpräfix) je EU-Land. */
    private static final Map<String, Pattern> EU_FORMATS = Map.ofEntries(
            Map.entry("AT", Pattern.compile("U\\d{8}")),
            Map.entry("BE", Pattern.compile("[01]\\d{9}")),
            Map.entry("BG", Pattern.compile("\\d{9,10}")),
            Map.entry("CY", Pattern.compile("\\d{8}[A-Z]")),
            Map.entry("CZ", Pattern.compile("\\d{8,10}")),
            Map.entry("DE", Pattern.compile("[1-9]\\d{8}")),
            Map.entry("DK", Pattern.compile("\\d{8}")),
            Map.entry("EE", Pattern.compile("\\d{9}")),
            Map.entry("EL", Pattern.compile("\\d{9}")),
            Map.entry("ES", Pattern.compile("[A-Z0-9]\\d{7}[A-Z0-9]")),
            Map.entry("FI", Pattern.compile("\\d{8}")),
            Map.entry("FR", Pattern.compile("[A-Z0-9]{2}\\d{9}")),
            Map.entry("HR", Pattern.compile("\\d{11}")),
            Map.entry("HU", Pattern.compile("\\d{8}")),
            Map.entry("IE", Pattern.compile("\\d{7}[A-W][A-I]?|\\d[A-Z+*]\\d{5}[A-W]")),
            Map.entry("IT", Pattern.compile("\\d{11}")),
            Map.entry("LT", Pattern.compile("\\d{9}|\\d{12}")),
            Map.entry("LU", Pattern.compile("\\d{8}")),
            Map.entry("LV", Pattern.compile("\\d{11}")),
            Map.entry("MT", Pattern.compile("\\d{8}")),
            Map.entry("NL", Pattern.compile("\\d{9}B\\d{2}")),
            Map.entry("PL", Pattern.compile("\\d{10}")),
            Map.entry("PT", Pattern.compile("\\d{9}")),
            Map.entry("RO", Pattern.compile("\\d{2,10}")),
            Map.entry("SE", Pattern.compile("\\d{10}01")),
            Map.entry("SI", Pattern.compile("\\d{8}")),
            Map.entry("SK", Pattern.compile("\\d{10}")),
            Map.entry("XI", Pattern.compile("\\d{9}(\\d{3})?"))
    );

    private VatIdValidator() {
    }

    /**
     * @return leeres Optional, wenn die USt-IdNr. gültig ist,
     *         sonst eine deutschsprachige Fehlermeldung.
     */
    public static Optional<String> validate(String vatId) {
        if (vatId == null || vatId.isBlank()) {
            return Optional.of("USt-IdNr. fehlt.");
        }
        String normalized = normalize(vatId);
        if (normalized.length() < 4
                || !Character.isLetter(normalized.charAt(0))
                || !Character.isLetter(normalized.charAt(1))) {
            return Optional.of("USt-IdNr. muss mit einem Länderpräfix beginnen, z. B. DE.");
        }
        String country = normalized.substring(0, 2);
        String body = normalized.substring(2);

        Pattern format = EU_FORMATS.get(country);
        if (format == null) {
            return Optional.of("Unbekanntes Länderpräfix \"" + country + "\".");
        }
        if (!format.matcher(body).matches()) {
            return Optional.of("USt-IdNr. entspricht nicht dem Format für " + country + ".");
        }
        if ("DE".equals(country) && !checkDigitValidDe(body)) {
            return Optional.of("Prüfziffer der deutschen USt-IdNr. ist ungültig.");
        }
        return Optional.empty();
    }

    public static boolean isValid(String vatId) {
        return validate(vatId).isEmpty();
    }

    /** Entfernt Leerzeichen, Punkte und Bindestriche und wandelt in Großbuchstaben um. */
    public static String normalize(String vatId) {
        return vatId.replaceAll("[\\s.\\-]", "").toUpperCase();
    }

    /** Prüfziffernverfahren ISO 7064, MOD 11,10 für deutsche USt-IdNrn. */
    private static boolean checkDigitValidDe(String digits) {
        int product = 10;
        for (int i = 0; i < 8; i++) {
            int sum = (Character.getNumericValue(digits.charAt(i)) + product) % 10;
            if (sum == 0) {
                sum = 10;
            }
            product = (2 * sum) % 11;
        }
        int checkDigit = 11 - product;
        if (checkDigit == 10) {
            checkDigit = 0;
        }
        return checkDigit == Character.getNumericValue(digits.charAt(8));
    }
}
