"""Ack ID helpers shared between the scheduler and the FastAPI
endpoints (SP27 Task 1).

Lifted from ``scheduler.py:_ack_count_summary`` +
``_ack_synthetic_source_batch_id`` + ``_277ca_synthetic_source_batch_id`` —
all three had inline copies in both ``scheduler.py`` and ``api.py``
prior to this SP. Both callers now import from this module.

Helpers
-------
``ack_count_summary(result)``
    Aggregate ``(received, accepted, rejected, ack_code)`` from a
    parsed ``ParseResult999``, trusting set-level IK5 over the
    functional-group AK9. Gainwell's MFT ships AK9 segments that
    contradict the per-set IK5 (e.g. ``AK9*A*1*1*1`` with
    ``IK5*A``), so trusting AK9's rejected count over-reports.
    See scheduler commit ``6507a8c`` for the operational context.

``ack_synthetic_source_batch_id(icn, *, pcn, source_filename)``
    Build a unique-per-file ``batches.id`` for a 999 that ships
    without its own source batch. Falls back to a hash suffix of
    the filename so daily pulls don't all collapse onto the same
    ICN. Gainwell's MFT ships every 999 with the default ICN
    ``000000001`` (per the scheduler docstring).

``two77ca_synthetic_source_batch_id(icn)``
    Same idea for a 277CA without its own source batch.
"""
from __future__ import annotations

import hashlib
from typing import Any


def ack_count_summary(result: Any) -> tuple[int, int, int, str]:
    """Aggregate ``(received, accepted, rejected, ack_code)`` from
    a ``ParseResult999``.

    Counts are derived from the set-level ``IK5`` responses
    (one per ``AK2`` in the 999), not the functional-group ``AK9``.
    Gainwell's MFT ships contradictory AK9 segments; the per-set
    IK5 is the authoritative per-claim accept/reject signal.
    """
    sets = result.set_responses
    received = len(sets)
    accepted = sum(1 for s in sets if s.set_accept_reject.code == "A")
    rejected = received - accepted
    if rejected == 0:
        code = "A"
    elif accepted == 0:
        code = "R"
    else:
        code = "P"
    return (received, accepted, rejected, code)


def ack_synthetic_source_batch_id(
    interchange_control_number: str,
    *,
    pcn: str | None = None,
    source_filename: str | None = None,
) -> str:
    """Synthetic ``batches.id`` for a received 999 with no source batch.

    Precedence for the human-readable part of the id (column is
    ``VARCHAR(32)``):

      1. ``999-{pcn}-{hash8}`` if ``AK2`` set_control_number is
         present (the common case). 4 + 9 + 1 + 8 = 22 chars max.
      2. ``999-{icn}-{hash8}`` if no AK2 (envelope-only 999).
      3. ``999-{hash12}`` if no filename either (shouldn't happen
         in production).
    """
    short_hash = ""
    if source_filename:
        short_hash = hashlib.sha1(source_filename.encode("utf-8")).hexdigest()[:8]
    if pcn and pcn.strip():
        return f"999-{pcn.strip()}-{short_hash}"
    icn = (interchange_control_number or "").strip() or "000000001"
    if short_hash:
        return f"999-{icn}-{short_hash}"
    return f"999-{short_hash or icn}"


def two77ca_synthetic_source_batch_id(interchange_control_number: str) -> str:
    """Synthetic ``batches.id`` for a received 277CA with no source batch."""
    return f"277CA-{(interchange_control_number or '').strip() or '000000001'}"
