"""Lead finder using Geoapify (free key, no card) + public contact-page email lookup."""
import os
import re
import time
import requests
from concurrent.futures import ThreadPoolExecutor, wait

GEOCODE_URL = "https://api.geoapify.com/v1/geocode/search"
PLACES_URL = "https://api.geoapify.com/v2/places"
EMAIL_RE = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
JUNK = ("example.", "sentry", "wixpress", "domain.com", "yourname", ".png", ".jpg", ".webp", ".svg", ".gif")
HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; LeadHunterBot/1.0)"}

# business word -> Geoapify category (hierarchical keys; parent keys include their children)
CATEGORY_MAP = {
    "dentist": "healthcare.dentist", "dental": "healthcare.dentist",
    "doctor": "healthcare.clinic_or_praxis", "clinic": "healthcare.clinic_or_praxis",
    "hospital": "healthcare.hospital", "pharmacy": "healthcare.pharmacy", "medical store": "healthcare.pharmacy",
    "restaurant": "catering.restaurant", "cafe": "catering.cafe", "coffee": "catering.cafe",
    "fast food": "catering.fast_food", "bakery": "commercial.food_and_drink.bakery",
    "hotel": "accommodation.hotel", "guest house": "accommodation.guest_house", "hostel": "accommodation.hostel",
    "school": "education.school", "college": "education.college", "university": "education.university",
    "kindergarten": "childcare.kindergarten", "driving school": "education.driving_school",
    "gym": "sport.fitness", "fitness": "sport.fitness",
    "salon": "service.beauty", "beauty": "service.beauty", "barber": "service.beauty.hairdresser",
    "hairdresser": "service.beauty.hairdresser", "spa": "service.beauty.spa",
    "mechanic": "service.vehicle.repair", "car repair": "service.vehicle.repair", "workshop": "service.vehicle.repair",
    "car wash": "service.vehicle.car_wash", "petrol": "service.vehicle.fuel", "fuel": "service.vehicle.fuel",
    "bank": "service.financial.bank", "atm": "service.financial.atm",
    "lawyer": "office.lawyer", "advocate": "office.lawyer", "accountant": "office.accountant",
    "real estate": "office.estate_agent", "estate agent": "office.estate_agent", "property": "office.estate_agent",
    "travel agent": "service.travel_agency", "travel agency": "service.travel_agency",
    "software": "office.it", "it company": "office.it", "marketing": "office.advertising_agency",
    "advertising": "office.advertising_agency", "insurance": "office.insurance", "architect": "office.architect",
    "vet": "pet.veterinary", "veterinary": "pet.veterinary", "pet shop": "pet.shop",
    "supermarket": "commercial.supermarket", "grocery": "commercial.supermarket", "mart": "commercial.supermarket",
    "clothes": "commercial.clothing", "clothing": "commercial.clothing", "boutique": "commercial.clothing",
    "shoes": "commercial.clothing.shoes", "electronics": "commercial.elektronics", "mobile": "commercial.elektronics",
    "jewelry": "commercial.jewelry", "jeweller": "commercial.jewelry", "furniture": "commercial.furniture_and_interior",
    "books": "commercial.books", "stationery": "commercial.stationery", "florist": "commercial.florist",
    "optician": "commercial.health_and_beauty.optician", "hardware": "commercial.houseware_and_hardware",
    "electrician": "service.electrician", "tailor": "service.tailor", "laundry": "service.cleaning.laundry",
    "dry cleaner": "service.cleaning.dry_cleaning", "photographer": "service.photographer",
    "wedding": "commercial.wedding", "factory": "production.factory", "mosque": "religion.place_of_worship.islam",
}
BROAD = "commercial,service,office,catering,healthcare,education,accommodation,production"


def guess_category(what: str):
    """Return (categories, name_filter). Unknown words fall back to a broad name search."""
    w = what.lower().strip()
    singular = re.sub(r"(ies)$", "y", w) if w.endswith("ies") else re.sub(r"(es|s)$", "", w) if len(w) > 4 else w
    for cand in (w, singular):
        if cand in CATEGORY_MAP:
            return CATEGORY_MAP[cand], None
    for key, cat in CATEGORY_MAP.items():  # key appears inside the phrase, e.g. "dental clinics"
        if key in w or key in singular:
            return cat, None
    return BROAD, singular


def geocode(place: str) -> dict:
    r = requests.get(GEOCODE_URL, timeout=10, params={
        "text": place, "format": "json", "limit": 1, "apiKey": os.environ["GEOAPIFY_API_KEY"]})
    if r.status_code != 200:
        raise RuntimeError(f"Geoapify geocoding error {r.status_code}")
    res = r.json().get("results", [])
    if not res:
        raise RuntimeError(f"I couldn't find the place '{place}'.")
    return res[0]


def _first(*vals):
    for v in vals:
        if v:
            return str(v).strip()
    return ""


def search_places(what: str, where: str, limit: int = 20) -> list[dict]:
    limit = max(1, min(limit, 60))
    loc = geocode(where)
    cats, name = guess_category(what)
    params = {"categories": cats, "limit": limit, "lang": "en", "apiKey": os.environ["GEOAPIFY_API_KEY"]}
    if name:
        params["name"] = name
    if loc.get("place_id"):
        params["filter"] = f"place:{loc['place_id']}"
    else:
        params["filter"] = f"circle:{loc['lon']},{loc['lat']},15000"
    params["bias"] = f"proximity:{loc['lon']},{loc['lat']}"
    r = requests.get(PLACES_URL, params=params, timeout=15)
    if r.status_code != 200:
        raise RuntimeError(f"Geoapify places error {r.status_code}: {r.text[:150]}")
    out = []
    for f in r.json().get("features", []):
        p = f.get("properties", {})
        if not p.get("name"):
            continue  # unnamed places are useless as leads
        raw = (p.get("datasource") or {}).get("raw") or {}
        contact = p.get("contact") or {}
        lat, lon = p.get("lat"), p.get("lon")
        out.append({
            "name": p["name"],
            "phone": _first(contact.get("phone"), p.get("phone"), raw.get("phone"), raw.get("contact:phone"), raw.get("contact:mobile")),
            "email": _first(contact.get("email"), p.get("email"), raw.get("email"), raw.get("contact:email")).lower(),
            "website": _first(p.get("website"), contact.get("website"), raw.get("website"), raw.get("contact:website")),
            "address": p.get("formatted", ""),
            "maps_url": f"https://www.google.com/maps/search/?api=1&query={lat},{lon}" if lat and lon else "",
        })
    return out


def _find_email(site: str) -> str:
    if not site.startswith("http"):
        site = "http://" + site
    base = site.rstrip("/")
    for url in (base, base + "/contact"):
        try:
            r = requests.get(url, headers=HEADERS, timeout=4)
            if r.status_code != 200:
                continue
            found = [e for e in EMAIL_RE.findall(r.text) if not any(j in e.lower() for j in JUNK)]
            if found:
                return found[0].lower()
        except requests.RequestException:
            continue
    return ""


def enrich_emails(leads: list[dict], budget_seconds: float = 7.0) -> list[dict]:
    """Look for public emails on websites, but never run longer than the time budget."""
    targets = [l for l in leads if l["website"] and not l["email"]]
    if not targets:
        return leads
    ex = ThreadPoolExecutor(max_workers=10)
    futs = {ex.submit(_find_email, l["website"]): l for l in targets}
    done, _ = wait(futs, timeout=budget_seconds)
    for f in done:
        try:
            futs[f]["email"] = f.result()
        except Exception:
            pass
    ex.shutdown(wait=False, cancel_futures=True)
    return leads


def dedupe(leads: list[dict]) -> list[dict]:
    seen, out = set(), []
    for l in leads:
        k = (l["name"].lower(), l["address"][:30].lower())
        if k not in seen:
            seen.add(k)
            out.append(l)
    return out
