"""Generate a Notice of Readiness (NOR) from voyage details.

A NOR is tendered by the master on arrival to start laytime counting. Getting the
wording and the tendered timestamp right matters commercially, so this builds it
from structured data instead of editing last voyage's Word file.

    python3 nor_generator.py --config voyage.json --out NOR_ARIA.txt

Standard library only.
"""

from __future__ import annotations

import argparse
import json
import sys
from dataclasses import dataclass
from datetime import datetime, timezone

NOR_TEMPLATE = """\
NOTICE OF READINESS

Vessel:            {vessel}
IMO number:        {imo}
Flag:              {flag}
Master:            {master}

Port:              {port}
Location:          {location}
Cargo:             {cargo}
Operation:         {operation}

Arrived:           {arrived}
NOR tendered:      {tendered}
Tendered to:       {tendered_to}

Dear Sirs,

Please be advised that the above vessel arrived at {location}, {port} on
{arrived}, and is in all respects ready to {operation_verb} the cargo of
{cargo} in accordance with the terms of the governing charter party.

Notice of Readiness is hereby tendered at {tendered}.

Laytime to commence in accordance with the charter party.

Master, {vessel}
{master}
"""

OPERATION_VERBS = {"loading": "load", "discharging": "discharge"}


@dataclass(frozen=True)
class Voyage:
    vessel: str
    imo: str
    flag: str
    master: str
    port: str
    location: str
    cargo: str
    operation: str
    arrived: str
    tendered_to: str


def parse_ts(value: str) -> datetime:
    """Accept 'YYYY-MM-DD HH:MM' and normalise to an aware UTC datetime."""
    try:
        naive = datetime.strptime(value.strip(), "%Y-%m-%d %H:%M")
    except ValueError as exc:
        raise ValueError(
            f"Timestamp {value!r} must look like '2026-08-23 14:30'"
        ) from exc
    return naive.replace(tzinfo=timezone.utc)


def format_ts(moment: datetime) -> str:
    return moment.strftime("%d %B %Y at %H:%M UTC")


def validate_imo(imo: str) -> str:
    """IMO numbers are 7 digits where the 7th is a checksum of the first six."""
    digits = imo.strip().upper().removeprefix("IMO").strip()
    if not (digits.isdigit() and len(digits) == 7):
        raise ValueError(f"IMO {imo!r} must be 7 digits")
    checksum = sum(int(d) * (7 - i) for i, d in enumerate(digits[:6])) % 10
    if checksum != int(digits[6]):
        raise ValueError(f"IMO {digits} fails its check digit")
    return f"IMO {digits}"


def build_nor(voyage: Voyage, tendered: datetime) -> str:
    operation = voyage.operation.strip().lower()
    if operation not in OPERATION_VERBS:
        raise ValueError(
            f"operation must be one of {sorted(OPERATION_VERBS)}, got {voyage.operation!r}"
        )

    arrived = parse_ts(voyage.arrived)
    if tendered < arrived:
        raise ValueError("NOR cannot be tendered before the vessel arrived")

    return NOR_TEMPLATE.format(
        vessel=voyage.vessel.upper(),
        imo=validate_imo(voyage.imo),
        flag=voyage.flag,
        master=voyage.master,
        port=voyage.port,
        location=voyage.location,
        cargo=voyage.cargo,
        operation=operation,
        operation_verb=OPERATION_VERBS[operation],
        arrived=format_ts(arrived),
        tendered=format_ts(tendered),
        tendered_to=voyage.tendered_to,
    )


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Generate a Notice of Readiness.")
    parser.add_argument("--config", required=True, help="JSON file with voyage details")
    parser.add_argument("--out", help="Write to this file instead of stdout")
    parser.add_argument(
        "--tendered",
        help="Tender time as 'YYYY-MM-DD HH:MM' UTC (default: now)",
    )
    args = parser.parse_args(argv)

    try:
        with open(args.config, encoding="utf-8") as handle:
            payload = json.load(handle)
        voyage = Voyage(**payload)
    except (OSError, json.JSONDecodeError) as exc:
        print(f"Could not read {args.config}: {exc}", file=sys.stderr)
        return 1
    except TypeError as exc:
        print(f"Config is missing or has unexpected fields: {exc}", file=sys.stderr)
        return 1

    tendered = parse_ts(args.tendered) if args.tendered else datetime.now(timezone.utc)

    try:
        notice = build_nor(voyage, tendered)
    except ValueError as exc:
        print(f"Refusing to issue NOR: {exc}", file=sys.stderr)
        return 1

    if args.out:
        with open(args.out, "w", encoding="utf-8") as handle:
            handle.write(notice)
        print(f"Wrote {args.out}")
    else:
        print(notice)
    return 0


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