"""ICAO Doc 9303 TD3 (passport) MRZ check-digit calculator.

Reference: ICAO Doc 9303 Machine Readable Travel Documents, Part 3, Volume 1,
Sec. IV "Check Digit Calculation".

MRZ Line 2 layout (44 chars):
   1-9   passport number (9, '<'-padded)
   10    passport number check digit
   11-13 issuing state / nationality (3)
   14-19 birth date (6 YYMMDD)
   20    birth date check digit
   21    sex (M/F/X)
   22-27 expiry date (6 YYMMDD)
   28    expiry date check digit
29-42 optional / personal data (14, '<'-padded)
    43    optional data check digit
    44    composite check digit (over positions 1-10, 14-20, 22-43)
"""

WEIGHTS = (7, 3, 1)


def to_value(ch: str) -> int:
    """Map a single MRZ character to its numeric value.

    Digits 0-9 map to themselves, A-Z map to 10..35, '<' maps to 0.
    """
    ch = ch.upper()
    if ch == "<":
        return 0
    if ch.isdigit():
        return int(ch)
    return ord(ch) - ord("A") + 10


def check_digit(data: str) -> int:
    """Compute the single check digit over ``data`` (e.g. 'MK565477' -> 0)."""
    total = sum(to_value(ch) * WEIGHTS[i % 3] for i, ch in enumerate(data))
    return total % 10


def _padded(raw: str, n: int) -> str:
    """Pad/truncate a raw field to exactly ``n`` chars with '<'."""
    return raw.upper().ljust(n, "<")[:n]


def build_line1(surname: str, given_name: str = "",
                type_code: str = "PV", country: str = "MMR") -> str:
    """Build the full 44-char MRZ line 1.

    Line 1 layout (44 chars):
       1     'P'
       2     type (V / I / <)
       3-5   country
       6-44  surname<<given_name padded with '<'
    """
    surname = surname.upper().replace(" ", "<")
    given = given_name.upper().replace(" ", "<") if given_name else ""
    type_char = type_code[1] if len(type_code) > 1 and type_code[1] != "<" else "<"
    country = country.upper()[:3]
    id_part = surname + "<<" + given if given else surname
    line1 = "P" + type_char + country + id_part
    return line1.ljust(44, "<")[:44]


def build_line2(passport_no, birth_date, expiry_date, sex="M",
                issuing_state="MMR", personal_no=""):
    """Build the full 44-char MRZ line 2 with all check digits.

    Line 2 layout (44 chars):
       1-9   passport number (9, '<'-padded)
       10    passport number check digit
       11-13 issuing state
       14-19 birth date (YYMMDD)
       20    birth date check digit
       21    sex (M/F)
       22-27 expiry date (YYMMDD)
       28    expiry date check digit
29-42 optional / personal data (14, '<'-padded)
        43    optional data check digit
        44    composite check digit (over positions 1-10, 14-20, 22-43)
    """
    p_d = _padded(passport_no, 9)
    b_d = _padded(birth_date, 6)
    e_d = _padded(expiry_date, 6)
    n_d = _padded(personal_no, 14)

    p_s = f"{p_d}{check_digit(p_d)}"
    b_s = f"{b_d}{check_digit(b_d)}"
    e_s = f"{e_d}{check_digit(e_d)}"
    if personal_no.strip("<"):
        n_s = f"{n_d}{check_digit(n_d)}"
    else:
        # 可选域未使用时，第43位校验可为 0 或 '<'（ICAO 9303 Part 4 允许），
        # 缅甸 mm_girl 模板实际用 '<'
        n_s = n_d + "<"

    line2_43 = p_s + issuing_state + b_s + sex + e_s + n_s
    # ICAO 9303 复合校验位只覆盖位置 1-10、14-20、22-43，
    # 即护照+生日+有效期+可选域（各带校验位），排除国籍(11-13)和性别(21)。
    total = check_digit(p_s + b_s + e_s + n_s)

    return line2_43 + str(total)


def build_mrz(passport_no: str, surname: str, given_name: str = "",
              birth_date: str = "000206", expiry_date: str = "301123",
              sex: str = "M", issuing_state: str = "MMR",
              type_code: str = "PV", country: str = "MMR") -> tuple:
    """Return (line1, line2) — full 2×44 char MRZ."""
    l1 = build_line1(surname, given_name, type_code, country)
    l2 = build_line2(passport_no, birth_date, expiry_date, sex, issuing_state)
    return l1, l2


if __name__ == "__main__":
    # --- reference verification ---
    l2 = build_line2("MK565477", "000206", "301123", sex="F")
    print("line2  :", l2)
    print("length :", len(l2))
    assert len(l2) == 44, f"expected 44, got {len(l2)}: {l2}"
    assert l2[9] == "0", l2[9]      # passport check  @10
    assert l2[19] == "0", l2[19]    # birth check     @20
    assert l2[27] == "8", l2[27]    # expiry check    @28
    assert l2[42] == "<", l2[42]    # optional check  @43 (empty -> '<')
    assert l2[43] == "6", l2[43]    # composite check @44
    print("✓ reference: positions 10/20/28/43/44 all match")

    # --- line 1 test ---
    # reference: "EI AM" is the full surname (space→<), no separate given name
    l1 = build_line1("EI AM", "", "PV", "MMR")
    print("line1  :", l1)
    assert len(l1) == 44, f"expected 44, got {len(l1)}: {l1}"
    assert l1[:10] == "PVMMREI<AM", l1[:10]
    print("✓ line1:  matches reference")

    # --- random demo ---
    l1d, l2d = build_mrz("AU185097", "AHMED", "DANIEL",
                          "990115", "331107", "M", "MMR", "PV", "MMR")
    print("\n--- random demo ---")
    print("AHMED/DANIEL line1:", l1d)
    print("AHMED/DANIEL line2:", l2d)
    assert len(l1d) == len(l2d) == 44
    print("✓ demo: dual-line self-check passed")