#!/usr/bin/env python3
"""Find nearby places using OpenStreetMap (Overpass + Nominatim). No API keys needed.

Usage:
    # By coordinates
    python find_nearby.py --lat 36.17 --lon -115.14 --type restaurant --radius 1500

    # By address/city/zip (auto-geocoded)
    python find_nearby.py --near "Times Square, New York" --type cafe --radius 1000
    python find_nearby.py --near "90210" --type pharmacy

    # Multiple types
    python find_nearby.py --lat 36.17 --lon -115.14 --type restaurant --type bar

    # JSON output for programmatic use
    python find_nearby.py --near "downtown las vegas" --type restaurant --json
"""

import argparse
import json
import math
import sys
import urllib.parse
import urllib.request
from typing import Any

OVERPASS_URLS = [
    "https://overpass-api.de/api/interpreter",
    "https://overpass.kumi.systems/api/interpreter",
]
NOMINATIM_URL = "https://nominatim.openstreetmap.org/search"
USER_AGENT = "HermesAgent/1.0 (find-nearby skill)"
TIMEOUT = 15


def _http_get(url: str) -> Any:
    req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
    with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
        return json.loads(r.read())


def _http_post(url: str, data: str) -> Any:
    req = urllib.request.Request(
        url, data=data.encode(), headers={"User-Agent": USER_AGENT}
    )
    with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
        return json.loads(r.read())


def haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
    """Distance in meters between two coordinates."""
    R = 6_371_000
    rlat1, rlat2 = math.radians(lat1), math.radians(lat2)
    dlat = math.radians(lat2 - lat1)
    dlon = math.radians(lon2 - lon1)
    a = math.sin(dlat / 2) ** 2 + math.cos(rlat1) * math.cos(rlat2) * math.sin(dlon / 2) ** 2
    return R * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))


def geocode(query: str) -> tuple[float, float]:
    """Convert address/city/zip to coordinates via Nominatim."""
    params = urllib.parse.urlencode({"q": query, "format": "json", "limit": 1})
    results = _http_get(f"{NOMINATIM_URL}?{params}")
    if not results:
        print(f"Error: Could not geocode '{query}'. Try a more specific address.", file=sys.stderr)
        sys.exit(1)
    return float(results[0]["lat"]), float(results[0]["lon"])


def find_nearby(lat: float, lon: float, types: list[str], radius: int = 1500, limit: int = 15) -> list[dict]:
    """Query Overpass for nearby amenities."""
    # Build Overpass QL query
    type_filters = "".join(
        f'nwr["amenity"="{t}"](around:{radius},{lat},{lon});' for t in types
    )
    query = f"[out:json][timeout:{TIMEOUT}];({type_filters});out center tags;"

    # Try each Overpass server
    data = None
    for url in OVERPASS_URLS:
        try:
            data = _http_post(url, f"data={urllib.parse.quote(query)}")
            break
        except Exception:
            continue

    if not data:
        return []

    # Parse results
    places = []
    for el in data.get("elements", []):
        tags = el.get("tags", {})
        name = tags.get("name")
        if not name:
            continue

        # Get coordinates (nodes have lat/lon directly, ways/relations use center)
        plat = el.get("lat") or (el.get("center", {}) or {}).get("lat")
        plon = el.get("lon") or (el.get("center", {}) or {}).get("lon")
        if plat is None or plon is None:
            continue

        dist = haversine(lat, lon, plat, plon)

        place = {
            "name": name,
            "type": tags.get("amenity", ""),
            "distance_m": round(dist),
            "lat": plat,
            "lon": plon,
            "maps_url": f"https://www.google.com/maps/search/?api=1&query={plat},{plon}",
            "directions_url": f"https://www.google.com/maps/dir/?api=1&origin={lat},{lon}&destination={plat},{plon}",
        }

        # Add useful optional fields
        if tags.get("cuisine"):
            place["cuisine"] = tags["cuisine"]
        if tags.get("opening_hours"):
            place["hours"] = tags["opening_hours"]
        if tags.get("phone"):
            place["phone"] = tags["phone"]
        if tags.get("website"):
            place["website"] = tags["website"]
        if tags.get("addr:street"):
            addr_parts = [tags.get("addr:housenumber", ""), tags.get("addr:street", "")]
            if tags.get("addr:city"):
                addr_parts.append(tags["addr:city"])
            place["address"] = " ".join(p for p in addr_parts if p)

        places.append(place)

    # Sort by distance, limit results
    places.sort(key=lambda p: p["distance_m"])
    return places[:limit]


def main():
    parser = argparse.ArgumentParser(description="Find nearby places via OpenStreetMap")
    parser.add_argument("--lat", type=float, help="Latitude")
    parser.add_argument("--lon", type=float, help="Longitude")
    parser.add_argument("--near", type=str, help="Address, city, or zip code (geocoded automatically)")
    parser.add_argument("--type", action="append", dest="types", default=[], help="Place type (restaurant, cafe, bar, pharmacy, etc.)")
    parser.add_argument("--radius", type=int, default=1500, help="Search radius in meters (default: 1500)")
    parser.add_argument("--limit", type=int, default=15, help="Max results (default: 15)")
    parser.add_argument("--json", action="store_true", dest="json_output", help="Output as JSON")
    args = parser.parse_args()

    # Resolve coordinates
    if args.near:
        lat, lon = geocode(args.near)
    elif args.lat is not None and args.lon is not None:
        lat, lon = args.lat, args.lon
    else:
        print("Error: Provide --lat/--lon or --near", file=sys.stderr)
        sys.exit(1)

    if not args.types:
        args.types = ["restaurant"]

    places = find_nearby(lat, lon, args.types, args.radius, args.limit)

    if args.json_output:
        print(json.dumps({"origin": {"lat": lat, "lon": lon}, "results": places, "count": len(places)}, indent=2))
    else:
        if not places:
            print(f"No {'/'.join(args.types)} found within {args.radius}m")
            return
        print(f"Found {len(places)} places within {args.radius}m:\n")
        for i, p in enumerate(places, 1):
            dist_str = f"{p['distance_m']}m" if p["distance_m"] < 1000 else f"{p['distance_m']/1000:.1f}km"
            print(f"  {i}. {p['name']} ({p['type']}) — {dist_str}")
            if p.get("cuisine"):
                print(f"     Cuisine: {p['cuisine']}")
            if p.get("hours"):
                print(f"     Hours: {p['hours']}")
            if p.get("address"):
                print(f"     Address: {p['address']}")
            print(f"     Map: {p['maps_url']}")
            print()


if __name__ == "__main__":
    main()
