"""Fetch foreign exchange rates and append them to a daily log.

The naive version of this script calls https://exchangerate-api.com, which is the
product's marketing page. It answers 301 with HTML, so response.json() raises and
response["rates"] never exists. The keyless JSON endpoint is open.er-api.com.

Beyond the URL, a script that runs on a schedule needs three things the original
had none of: a timeout (otherwise a hung connection blocks the cron slot
forever), a retry for the transient failure, and a check that the payload is
actually a success response before indexing into it.

    python3 fx_watch.py                          # USD -> TRY, BGN, EUR
    python3 fx_watch.py --base EUR --to TRY BGN
    python3 fx_watch.py --csv rates.csv          # append a daily row

Requires: requests
"""

from __future__ import annotations

import argparse
import csv
import sys
import time
from datetime import date
from pathlib import Path

import requests

ENDPOINT = "https://open.er-api.com/v6/latest/{base}"
TIMEOUT = 10
RETRIES = 3
BACKOFF = 2.0


def fetch_rates(base: str) -> dict:
    """Fetch rates, retrying transient failures with a widening backoff."""
    last_error: Exception | None = None

    for attempt in range(1, RETRIES + 1):
        try:
            response = requests.get(
                ENDPOINT.format(base=base),
                timeout=TIMEOUT,
                headers={"Accept": "application/json"},
            )
            response.raise_for_status()
            payload = response.json()
        except (requests.RequestException, ValueError) as exc:
            last_error = exc
            if attempt < RETRIES:
                time.sleep(BACKOFF ** attempt)
            continue

        # The API answers HTTP 200 with result="error" for a bad base currency,
        # so the status code alone is not enough to trust the body.
        if payload.get("result") != "success":
            raise RuntimeError(
                f"API returned an error for base {base}: "
                f"{payload.get('error-type', 'unknown')}"
            )
        return payload

    raise RuntimeError(f"Could not reach the rates API after {RETRIES} attempts: {last_error}")


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Fetch FX rates and optionally log them.")
    parser.add_argument("--base", default="USD", help="Base currency (default: USD)")
    parser.add_argument("--to", nargs="+", default=["TRY", "BGN", "EUR"],
                        help="Target currencies (default: TRY BGN EUR)")
    parser.add_argument("--csv", type=Path, help="Append a dated row to this CSV")
    args = parser.parse_args(argv)

    base = args.base.upper()
    targets = [code.upper() for code in args.to]

    try:
        payload = fetch_rates(base)
    except RuntimeError as exc:
        print(exc, file=sys.stderr)
        return 1

    rates = payload.get("rates", {})
    missing = [code for code in targets if code not in rates]
    if missing:
        print(f"Unknown currency code(s): {', '.join(missing)}", file=sys.stderr)
        return 1

    print(f"Base {base} — updated {payload.get('time_last_update_utc', 'unknown')}")
    for code in targets:
        print(f"  1 {base} = {rates[code]:.4f} {code}")

    if args.csv:
        row = {"date": date.today().isoformat(), "base": base}
        row.update({code: f"{rates[code]:.6f}" for code in targets})

        is_new = not args.csv.exists()
        with args.csv.open("a", newline="", encoding="utf-8") as handle:
            writer = csv.DictWriter(handle, fieldnames=list(row))
            if is_new:
                writer.writeheader()
            writer.writerow(row)
        print(f"\nAppended to {args.csv}")

    return 0


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