#!/usr/bin/env python3
"""Add IANA BGP well-known community status to a per-community CSV.

Usage:
    python3 enrich_iana_status.py IN.csv OUT.csv

Input is expected to contain a `community` column with values like `65535:666`.
The script prepends two columns:

    iana_name, iana_reference

The registry entries below are a local transcription of the IANA BGP
Well-known Communities registry used for the accompanying blog analysis.
"""

import csv
import sys
from pathlib import Path


ASSIGNED_COMMUNITIES = {
    0xFFFF0000: ("GRACEFUL_SHUTDOWN", "RFC 8326"),
    0xFFFF0001: ("ACCEPT_OWN", "RFC 7611"),
    0xFFFF0002: ("ROUTE_FILTER_TRANSLATED_v4", "draft-l3vpn-legacy-rtc-00"),
    0xFFFF0003: ("ROUTE_FILTER_v4", "draft-l3vpn-legacy-rtc-00"),
    0xFFFF0004: ("ROUTE_FILTER_TRANSLATED_v6", "draft-l3vpn-legacy-rtc-00"),
    0xFFFF0005: ("ROUTE_FILTER_v6", "draft-l3vpn-legacy-rtc-00"),
    0xFFFF0006: ("LLGR_STALE", "RFC 9494"),
    0xFFFF0007: ("NO_LLGR", "RFC 9494"),
    0xFFFF0008: ("accept-own-nexthop", "draft-agrewal-idr-accept-own-nexthop-00"),
    0xFFFF0009: ("Standby PE", "RFC 9026"),
    0xFFFF029A: ("BLACKHOLE", "RFC 7999"),
    0xFFFFFF01: ("NO_EXPORT", "RFC 1997"),
    0xFFFFFF02: ("NO_ADVERTISE", "RFC 1997"),
    0xFFFFFF03: ("NO_EXPORT_SUBCONFED", "RFC 1997"),
    0xFFFFFF04: ("NOPEER", "RFC 3765"),
}


UNASSIGNED_RANGES = [
    (0xFFFF000A, 0xFFFF0299, "IANA FCFS range"),
    (0xFFFF029B, 0xFFFFFF00, "IANA Standards Action range"),
    (0xFFFFFF05, 0xFFFFFFFF, "IANA Standards Action range"),
]


def usage() -> None:
    print("usage: enrich_iana_status.py IN.csv OUT.csv", file=sys.stderr)


def community_to_integer(community: str) -> int:
    """Convert A:B community notation to its 32-bit integer value."""
    high, low = community.split(":")
    return (int(high) << 16) + int(low)


def iana_status(value: int) -> tuple[str, str]:
    """Return (name, reference) for a 32-bit community value."""
    if value in ASSIGNED_COMMUNITIES:
        return ASSIGNED_COMMUNITIES[value]

    for start, end, reference in UNASSIGNED_RANGES:
        if start <= value <= end:
            return "Unassigned", reference

    return "Other", "Outside 0xFFFF well-known range"


def enrich(input_path: Path, output_path: Path) -> None:
    with input_path.open() as input_file, output_path.open("w", newline="") as output_file:
        reader = csv.DictReader(input_file)
        if reader.fieldnames is None:
            raise ValueError(f"no CSV header found in {input_path}")

        fieldnames = ["iana_name", "iana_reference", *reader.fieldnames]
        writer = csv.DictWriter(output_file, fieldnames=fieldnames)
        writer.writeheader()

        for row in reader:
            name, reference = iana_status(community_to_integer(row["community"]))
            writer.writerow({"iana_name": name, "iana_reference": reference, **row})


def main() -> int:
    if len(sys.argv) != 3:
        usage()
        return 2

    enrich(Path(sys.argv[1]), Path(sys.argv[2]))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
