"""Tests for ``cyclone.npi`` — NPI checksum + Tax ID (EIN) format validation.

Pure local validation, no NPPES calls. Covers the NPPES-published Luhn
checksum (with prefix ``80840``) and a small set of obvious EIN typo cases.
"""
from __future__ import annotations

import pytest

from cyclone.npi import (
    _luhn_check_digit,
    is_valid_npi,
    is_valid_tax_id,
    npi_checksum,
    normalize_tax_id,
)


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


def test_luhn_check_digit_known_sequence():
    """Standard Luhn for the empty body returns 0.

    With no digits the sum is 0, so (10 - 0 % 10) % 10 = 0.
    """
    assert _luhn_check_digit("") == 0


def test_luhn_check_digit_single_digit():
    """For a single body digit the check digit is the standard Luhn value.

    With the corrected "double at rightmost" pattern: "0" doubles to 0
    (total=0, check=0); "1" doubles to 2 (total=2, check=(10-2)%10=8).
    """
    assert _luhn_check_digit("0") == 0
    assert _luhn_check_digit("1") == 8


def test_luhn_check_digit_nppes_published():
    """CMS-published example: body 123456789 → check digit 3.

    Per https://www.cms.gov/.../NPIcheckdigit.pdf the Luhn sum of the
    prefixed body ``80840123456789`` is 67, so check digit = (10-7)%10 = 3,
    giving the full NPI ``1234567893``.
    """
    assert _luhn_check_digit("80840123456789") == 3


# ---------------------------------------------------------------------------
# npi_checksum
# ---------------------------------------------------------------------------


def test_npi_checksum_cms_published_body():
    """For NPI body 123456789 the check digit is 3 → NPI 1234567893.

    Confirmed against the CMS-published NPI Luhn example.
    """
    assert npi_checksum("123456789") == 3


def test_npi_checksum_rejects_non_digits():
    with pytest.raises(ValueError):
        npi_checksum("12345abc6")  # letter in body


def test_npi_checksum_rejects_wrong_length():
    with pytest.raises(ValueError):
        npi_checksum("12345")  # only 5 digits
    with pytest.raises(ValueError):
        npi_checksum("1234567890")  # 10 digits — includes the check digit


# ---------------------------------------------------------------------------
# is_valid_npi
# ---------------------------------------------------------------------------


def test_is_valid_npi_nppes_sample_is_true():
    """The CMS-published example NPI 1234567893 must validate."""
    assert is_valid_npi("1234567893") is True


def test_is_valid_npi_off_by_one_is_false():
    assert is_valid_npi("1234567894") is False


def test_is_valid_npi_all_zeros_is_false():
    """All zeros fails the Luhn check."""
    assert is_valid_npi("0000000000") is False


def test_is_valid_npi_rejects_empty():
    assert is_valid_npi("") is False


def test_is_valid_npi_rejects_none():
    assert is_valid_npi(None) is False  # type: ignore[arg-type]


def test_is_valid_npi_rejects_non_string():
    assert is_valid_npi(1234567890) is False  # type: ignore[arg-type]
    assert is_valid_npi(["1881068062"]) is False  # type: ignore[arg-type]


def test_is_valid_npi_rejects_short():
    assert is_valid_npi("123456789") is False  # 9 digits


def test_is_valid_npi_rejects_long():
    assert is_valid_npi("12345678901") is False  # 11 digits


def test_is_valid_npi_rejects_non_digits():
    assert is_valid_npi("188106806X") is False
    assert is_valid_npi("18810 68062") is False  # space


def test_is_valid_npi_all_ones_fails_luhn():
    """1111111111 has all-1 sum: alternating double = 1,2,1,2,..., 1+2=3 then
    collapse. Total for 10 digits body (body=9 of all 1's, sum doubled):
    Position from right (i=0..8): 1, 1, 1, 1, 1, 1, 1, 1, 1
    i even (not doubled): 1, 1, 1, 1, 1 → 5
    i odd (doubled): 1*2=2, 1*2=2, 1*2=2, 1*2=2 → 8
    Total body = 13. Body alone has check digit (10 - 13 % 10) % 10 = 7.
    So full NPI 1111111111's body (9 ones) check = 7, and 1 != 7 → invalid.
    """
    assert is_valid_npi("1111111111") is False


# ---------------------------------------------------------------------------
# is_valid_tax_id / normalize_tax_id
# ---------------------------------------------------------------------------


def test_is_valid_tax_id_touch_of_care_true():
    """The operator's reference EIN — Touch of Care Family Practice."""
    assert is_valid_tax_id("72-1587149") is True


def test_is_valid_tax_id_unformatted_true():
    assert is_valid_tax_id("721587149") is True


def test_is_valid_tax_id_00_prefix_rejected():
    """``00`` is reserved / never assigned by the IRS."""
    assert is_valid_tax_id("00-1234567") is False
    assert is_valid_tax_id("001234567") is False


def test_is_valid_tax_id_07_prefix_rejected():
    """``07`` is a campus prefix reserved for future use."""
    assert is_valid_tax_id("07-1234567") is False
    assert is_valid_tax_id("071234567") is False


def test_is_valid_tax_id_8x_prefix_rejected():
    """``80``–``89`` is the IRS Pension Plan Branch — never assigned otherwise."""
    assert is_valid_tax_id("80-1234567") is False
    assert is_valid_tax_id("89-1234567") is False


def test_is_valid_tax_id_rejects_malformed():
    assert is_valid_tax_id("not-an-ein") is False
    assert is_valid_tax_id("12345") is False
    assert is_valid_tax_id("1234567890") is False  # 10 digits
    assert is_valid_tax_id("12-345678") is False  # 8 digits after hyphen


def test_is_valid_tax_id_rejects_none_and_non_string():
    assert is_valid_tax_id(None) is False
    assert is_valid_tax_id(721587149) is False  # type: ignore[arg-type]


def test_normalize_tax_id_returns_plain_form():
    assert normalize_tax_id("72-1587149") == "721587149"
    assert normalize_tax_id("721587149") == "721587149"


def test_normalize_tax_id_strips_whitespace():
    assert normalize_tax_id("  72-1587149  ") == "721587149"


def test_normalize_tax_id_returns_none_for_invalid():
    assert normalize_tax_id("not-an-ein") is None
    assert normalize_tax_id(None) is None
    assert normalize_tax_id("") is None
    assert normalize_tax_id("12-345678") is None  # wrong digit count


def test_normalize_tax_id_keeps_reserved_prefix():
    """normalize_tax_id is a *structural* normalizer — it doesn't reject
    reserved prefixes. That's ``is_valid_tax_id``'s job. Operators who
    want to store 00-prefixed EINs as placeholders still get a clean
    9-digit string."""
    assert normalize_tax_id("00-1234567") == "001234567"