#!/usr/bin/env python3
"""Dependency-free command-line client for the public Time.Now JSON API."""

import argparse
import json
import os
import sys
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen


DEFAULT_API_BASE = "https://time.now/developer/api"
VERSION = "1.0.0"


class CLIError(Exception):
    pass


def _request_json(path, params=None):
    base = os.environ.get("TIMENOW_API_BASE", DEFAULT_API_BASE).rstrip("/")
    url = base + path
    if params:
        url += "?" + urlencode(params)
    request = Request(url, headers={
        "Accept": "application/json",
        "User-Agent": "timenow-cli/%s" % VERSION,
    })
    try:
        with urlopen(request, timeout=10) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as exc:
        try:
            payload = json.loads(exc.read().decode("utf-8"))
            message = payload.get("error") or payload.get("message")
        except Exception:
            message = None
        raise CLIError(message or "Time.Now API returned HTTP %s" % exc.code)
    except URLError as exc:
        raise CLIError("Could not reach Time.Now API: %s" % exc.reason)
    except (ValueError, OSError) as exc:
        raise CLIError("Invalid response from Time.Now API: %s" % exc)


def _build_parser():
    parser = argparse.ArgumentParser(
        prog="timenow",
        description="Current time and timezone data from the Time.Now API.",
    )
    parser.add_argument("--version", action="version", version="%(prog)s " + VERSION)
    parser.add_argument("--json", action="store_true", help="print raw JSON")
    subparsers = parser.add_subparsers(dest="command", required=True)

    time_parser = subparsers.add_parser("time", help="get time for an IANA timezone")
    time_parser.add_argument("timezone", nargs="?", default="UTC")
    time_parser.add_argument("--json", action="store_true", dest="command_json")

    zones_parser = subparsers.add_parser("zones", help="list or search IANA timezones")
    zones_parser.add_argument("--search", default="")
    zones_parser.add_argument("--limit", type=int, default=100)
    zones_parser.add_argument("--json", action="store_true", dest="command_json")

    ip_parser = subparsers.add_parser("ip", help="get time for your IP or an explicit IP")
    ip_parser.add_argument("address", nargs="?")
    ip_parser.add_argument("--json", action="store_true", dest="command_json")

    health_parser = subparsers.add_parser("health", help="check API availability")
    health_parser.add_argument("--json", action="store_true", dest="command_json")
    return parser


def _human_time(data):
    return "\n".join([
        "%s  %s" % (data.get("datetime", ""), data.get("timezone", "")),
        "Offset %s · %s · DST %s" % (
            data.get("utc_offset", ""),
            data.get("abbreviation", ""),
            "active" if data.get("dst") else "inactive",
        ),
        "Unix %s" % data.get("unixtime", ""),
    ])


def run(argv=None):
    args = _build_parser().parse_args(argv)
    raw_json = bool(args.json or getattr(args, "command_json", False))

    if args.command == "time":
        data = _request_json("/time", {"timezone": args.timezone})
        output = json.dumps(data, indent=2, ensure_ascii=False) if raw_json else _human_time(data)
    elif args.command == "zones":
        zones = _request_json("/timezone")
        search = args.search.lower()
        zones = [zone for zone in zones if search in zone.lower()][:max(1, args.limit)]
        output = json.dumps(zones, indent=2) if raw_json else "\n".join(zones)
    elif args.command == "ip":
        path = "/ip"
        if args.address:
            path += "/" + quote(args.address, safe="")
        data = _request_json(path)
        output = json.dumps(data, indent=2, ensure_ascii=False) if raw_json else _human_time(data)
    else:
        data = _request_json("/health")
        output = json.dumps(data, indent=2) if raw_json else "%s: %s (v%s)" % (
            data.get("service", "Time.Now API"),
            data.get("status", "unknown"),
            data.get("version", "?"),
        )

    print(output)
    return 0


def main():
    try:
        return run()
    except CLIError as exc:
        print("timenow: %s" % exc, file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())
