"""SP20 — NPI checksum + Tax ID format validation.

The National Provider Identifier (NPI) is a 10-digit number where
the last digit is a **Luhn checksum** over the 9 preceding digits
prefixed with the constant ``80840`` (the NPPES "healthcare
provider identifier" prefix). See CMS / HHS NPI Standard:

    https://www.cms.gov/medicare/health-care-provider-identifier

The Tax ID (EIN) is a 9-digit number, optionally formatted with a
hyphen after the second digit (``XX-XXXXXXX``). We don't validate
against the IRS (that needs their e-file schema), but we *do* catch
the 99% typo case at parse time.

Everything in this module is local — no NPPES, no network. Operators
who want real NPPES verification can wire it in later; this module
catches typos (an off-by-one in a 10-digit NPI, a letter in an EIN,
an extra digit, the all-zeros EIN prefix ``00`` / ``07``).
"""
from __future__ import annotations

import re

# NPPES prefix per the NPI Luhn algorithm. Prepended to the 9-digit
# NPI body before running the Luhn check.
_NPPES_PREFIX = "80840"


def npi_checksum(npi_body: str) -> int:
    """Compute the Luhn check digit for a 9-digit NPI body.

    ``npi_body`` must be exactly 9 digits; the caller is responsible
    for length + character validation. Returns the check digit (0–9).
    """
    if not npi_body.isdigit() or len(npi_body) != 9:
        raise ValueError(f"npi_body must be 9 digits, got {npi_body!r}")
    digits = _NPPES_PREFIX + npi_body
    return _luhn_check_digit(digits)


def is_valid_npi(npi: str | None) -> bool:
    """True if ``npi`` is a well-formed 10-digit NPI with valid checksum.

    Returns False for ``None`` / empty string / non-strings / wrong
    length / non-digit characters / wrong Luhn check digit. Doesn't
    call NPPES — see module docstring for why.

    >>> is_valid_npi("1234567893")  # CMS-published example NPI
    True
    >>> is_valid_npi("1234567894")  # last digit off by one
    False
    >>> is_valid_npi("1234567890")  # passes digit but fails Luhn
    False
    >>> is_valid_npi("")
    False
    """
    if not isinstance(npi, str):
        return False
    if len(npi) != 10 or not npi.isdigit():
        return False
    return npi[-1] == str(npi_checksum(npi[:-1]))


# ---------------------------------------------------------------------------
# Tax ID (EIN)
# ---------------------------------------------------------------------------


# EIN prefix table (subset). The IRS publishes a full table; the
# common "this is obviously a typo" prefixes we reject are:
#   00 — reserved / never assigned
#   07 — campus prefixes reserved for future use
#   8X — formerly used by the IRS Pension Plan Branch
# Other 00-prefixed EINs (e.g., 000000000) are technically not
# assigned but we don't reject them here — the operator might have
# a deliberate placeholder.
_EIN_FORBIDDEN_PREFIXES = {"00", "07"}
_EIN_RESERVED_PREFIX_8X = re.compile(r"^8\d$")

# 9 digits, optionally formatted as XX-XXXXXXX.
_EIN_FORMATTED = re.compile(r"^\d{2}-\d{7}$")
_EIN_PLAIN = re.compile(r"^\d{9}$")


def normalize_tax_id(tax_id: str | None) -> str | None:
    """Return ``tax_id`` in 9-digit plain form, or None if it's malformed.

    >>> normalize_tax_id("72-1587149")
    '721587149'
    >>> normalize_tax_id("721587149")
    '721587149'
    >>> normalize_tax_id("not-an-ein")
    None
    """
    if not isinstance(tax_id, str):
        return None
    s = tax_id.strip()
    if _EIN_FORMATTED.match(s):
        return s.replace("-", "")
    if _EIN_PLAIN.match(s):
        return s
    return None


def is_valid_tax_id(tax_id: str | None) -> bool:
    """True if ``tax_id`` is a 9-digit EIN (formatted or plain) with
    a non-reserved prefix.

    >>> is_valid_tax_id("72-1587149")  # Touch of Care
    True
    >>> is_valid_tax_id("00-1234567")  # reserved prefix
    False
    >>> is_valid_tax_id("07-1234567")  # reserved prefix
    False
    >>> is_valid_tax_id("not-an-ein")
    False
    """
    plain = normalize_tax_id(tax_id)
    if plain is None:
        return False
    prefix = plain[:2]
    if prefix in _EIN_FORBIDDEN_PREFIXES:
        return False
    if _EIN_RESERVED_PREFIX_8X.match(prefix):
        return False
    return True


# ---------------------------------------------------------------------------
# Luhn internals
# ---------------------------------------------------------------------------


def _luhn_check_digit(digits: str) -> int:
    """Return the Luhn check digit for ``digits``.

    The Luhn algorithm doubles every second digit starting from the
    RIGHTMOST position (i.e., the first digit doubled is the rightmost
    character of ``digits``). If the doubled value exceeds 9, subtract
    9. Sum all digits; the check digit is ``(10 - sum % 10) % 10``.

    ``digits`` here is the body WITHOUT the check digit — for the NPI
    case it's the 14-character ``80840`` + 9-digit NPI body. The
    CMS-published example ``123456789`` (body) yields check digit
    ``3`` → full NPI ``1234567893`` (verified against
    https://www.cms.gov/.../NPIcheckdigit.pdf).
    """
    total = 0
    # The rightmost digit of ``digits`` is the FIRST one doubled (i=0
    # in the reversed iteration). Per CMS, doubling starts at the
    # rightmost and alternates leftward.
    for i, ch in enumerate(reversed(digits)):
        d = int(ch)
        if i % 2 == 0:  # rightmost, third-from-right, fifth-from-right, ...
            d *= 2
            if d > 9:
                d -= 9
        total += d
    return (10 - total % 10) % 10
