"""Location awareness plugin — Standortermittlung ohne API-Key.

Fähigkeiten:
  - get_location()            – IP-basierte Standortermittlung (ip-api.com)
  - geocode(address)          – Adresse → Koordinaten (OpenStreetMap Nominatim)
  - reverse_geocode(lat, lon) – Koordinaten → Adresse
  - get_timezone(location)    – Zeitzone für Ort oder IP

Datenschutz: Es wird die externe IP des Servers / des Nutzers verwendet.
Keine Registrierung, kein API-Key erforderlich.
"""
from __future__ import annotations

import logging
from typing import Any

import httpx

from hivemind.plugins.base import Plugin, capability

log = logging.getLogger(__name__)

# Endpoints — alle kostenlos, kein API-Key erforderlich
IPAPI_URL       = "http://ip-api.com/json/{ip}?lang=de&fields=status,message,country,countryCode,region,regionName,city,zip,lat,lon,timezone,isp,org,as,query"
NOMINATIM_GEO   = "https://nominatim.openstreetmap.org/search?q={q}&format=json&addressdetails=1&limit=1&accept-language=de"
NOMINATIM_REV   = "https://nominatim.openstreetmap.org/reverse?lat={lat}&lon={lon}&format=json&accept-language=de"
WORLDTIME_URL   = "https://worldtimeapi.org/api/timezone/{tz}"

LOCATION_KEYWORDS = {
    # Deutsch
    "standort", "wo bin ich", "mein ort", "meine position", "meine stadt",
    "gps", "koordinaten", "adresse", "ort", "stadt", "postleitzahl", "plz",
    "land", "region", "bundesland", "geolocation", "geocoding", "zeitzone",
    "wo liegt", "entfernung", "karte", "hier", "da", "dort", "ip-standort", "ip-geolocation",
    "umgekehrtes geocoding", "ip standort", "bei mir", "mein ort", "mein standort", "mein gps", 
    "meine adresse", "meine stadt", "meine koordinaten", "meine zeitzone", "mein land", "mein region", 
    "mein bundesland", "wo bin ich", "wo befinde ich mich", "wo liege ich", "wo befinde ich mich",
    "wo bin ich gerade", "wo befinde ich mich gerade", "wo befinde ich mich aktuell",
    "wo bin ich aktuell", "wo befinde ich mich jetzt",
    # Englisch
    "location", "where am i", "my location", "position", "city", "address",
    "coordinates", "latitude", "longitude", "timezone", "country", "region",
    "geocode", "reverse geocode", "ip location",
}

_HEADERS = {"User-Agent": "HiveMind-LocationPlugin/1.0 (github.com/hivemind)"}


def _fmt_coord(v: Any, decimals: int = 5) -> str:
    try:
        return f"{float(v):.{decimals}f}"
    except (TypeError, ValueError):
        return str(v)


class LocationPlugin(Plugin):
    """Plugin für Standortermittlung und Geocoding."""

    name        = "location_info"
    version     = "1.0.0"
    description = "Standortermittlung per IP, Geocoding (Adresse ↔ Koordinaten) und Zeitzone via OpenStreetMap & ip-api.com — ohne API-Key"

    # ── IP-Geolocation ────────────────────────────────────────────────

    @capability("Ermittle den aktuellen Standort anhand der IP-Adresse (Land, Stadt, Koordinaten, ISP, Zeitzone)")
    async def get_location(self, ip: str = "") -> str:
        """Bestimme den geografischen Standort einer IP-Adresse.

        Args:
            ip: IPv4/IPv6-Adresse (leer = eigene externe IP)
        """
        target = ip.strip() if ip else ""
        url = IPAPI_URL.format(ip=target)
        try:
            async with httpx.AsyncClient(timeout=10, follow_redirects=True) as client:
                resp = await client.get(url, headers=_HEADERS)
                resp.raise_for_status()
                data = resp.json()

            if data.get("status") != "success":
                return f"Standort konnte nicht ermittelt werden: {data.get('message', 'Unbekannter Fehler')}"

            ip_used    = data.get("query", target or "eigene IP")
            city       = data.get("city", "–")
            region     = data.get("regionName", "–")
            country    = data.get("country", "–")
            country_cc = data.get("countryCode", "")
            zip_code   = data.get("zip", "–")
            lat        = data.get("lat", "–")
            lon        = data.get("lon", "–")
            tz         = data.get("timezone", "–")
            isp        = data.get("isp", "–")
            org        = data.get("org", "")

            flag = _cc_to_flag(country_cc)
            org_str = f" ({org})" if org and org != isp else ""

            return (
                f"{flag} Standort für IP {ip_used}:\n"
                f"  Ort:         {city}, {zip_code}, {region}, {country} {flag}\n"
                f"  Koordinaten: {_fmt_coord(lat)}, {_fmt_coord(lon)}\n"
                f"  Zeitzone:    {tz}\n"
                f"  ISP / Netz:  {isp}{org_str}"
            )

        except httpx.HTTPError as e:
            log.warning("location get_location HTTP error: %s", e)
            return f"HTTP-Fehler bei Standortabfrage: {e}"
        except Exception as e:
            log.error("location get_location error: %s", e)
            return f"Fehler bei Standortermittlung: {e}"

    # ── Geocoding ─────────────────────────────────────────────────────

    @capability("Konvertiere eine Adresse oder einen Ortsnamen in GPS-Koordinaten (Breitengrad, Längengrad)")
    async def geocode(self, address: str) -> str:
        """Suche nach einer Adresse und liefere Koordinaten.

        Args:
            address: Adresse, Ortsname oder POI (z.B. 'Brandenburger Tor, Berlin')
        """
        if not address or not address.strip():
            return "Bitte eine Adresse oder einen Ortsnamen angeben."

        url = NOMINATIM_GEO.format(q=address.strip().replace(" ", "+"))
        try:
            async with httpx.AsyncClient(timeout=10, follow_redirects=True) as client:
                resp = await client.get(url, headers=_HEADERS)
                resp.raise_for_status()
                results = resp.json()

            if not results:
                return f"Kein Ort gefunden für: \"{address}\""

            r         = results[0]
            display   = r.get("display_name", address)
            lat       = _fmt_coord(r.get("lat"))
            lon       = _fmt_coord(r.get("lon"))
            place_type = r.get("type", r.get("class", "Ort"))
            osm_id    = r.get("osm_id", "")

            addr_parts = r.get("address", {})
            country    = addr_parts.get("country", "")
            country_cc = addr_parts.get("country_code", "").upper()
            flag       = _cc_to_flag(country_cc)

            return (
                f"📍 Geocoding: \"{address}\"\n"
                f"  Vollständige Adresse: {display}\n"
                f"  Koordinaten:          {lat}° N, {lon}° E\n"
                f"  Typ:                  {place_type}\n"
                f"  Land:                 {flag} {country}\n"
                f"  OpenStreetMap ID:     {osm_id}"
            )

        except httpx.HTTPError as e:
            return f"HTTP-Fehler beim Geocoding: {e}"
        except Exception as e:
            log.error("location geocode error: %s", e)
            return f"Fehler beim Geocoding: {e}"

    # ── Reverse Geocoding ─────────────────────────────────────────────

    @capability("Ermittle eine Adresse aus GPS-Koordinaten (Reverse Geocoding)")
    async def reverse_geocode(self, lat: float | str, lon: float | str) -> str:
        """Wandle Koordinaten in eine lesbare Adresse um.

        Args:
            lat: Breitengrad (z.B. 52.5163)
            lon: Längengrad (z.B. 13.3779)
        """
        try:
            lat_f = float(str(lat).strip())
            lon_f = float(str(lon).strip())
        except (ValueError, TypeError):
            return f"Ungültige Koordinaten: lat={lat}, lon={lon}"

        if not (-90 <= lat_f <= 90) or not (-180 <= lon_f <= 180):
            return "Koordinaten außerhalb des gültigen Bereichs."

        url = NOMINATIM_REV.format(lat=lat_f, lon=lon_f)
        try:
            async with httpx.AsyncClient(timeout=10, follow_redirects=True) as client:
                resp = await client.get(url, headers=_HEADERS)
                resp.raise_for_status()
                data = resp.json()

            if "error" in data:
                return f"Kein Ort bei {lat_f}, {lon_f} gefunden."

            display   = data.get("display_name", "Unbekannt")
            addr      = data.get("address", {})
            road      = addr.get("road", "")
            house     = addr.get("house_number", "")
            city      = addr.get("city") or addr.get("town") or addr.get("village") or addr.get("municipality", "")
            postcode  = addr.get("postcode", "")
            state     = addr.get("state", "")
            country   = addr.get("country", "")
            country_cc = addr.get("country_code", "").upper()
            flag      = _cc_to_flag(country_cc)

            street = f"{road} {house}".strip() if road else ""
            short  = ", ".join(filter(None, [street, postcode + " " + city if postcode else city, state, country]))

            return (
                f"📍 Reverse Geocoding: {lat_f}, {lon_f}\n"
                f"  Adresse:     {flag} {short or display}\n"
                f"  Vollständig: {display}"
            )

        except httpx.HTTPError as e:
            return f"HTTP-Fehler beim Reverse Geocoding: {e}"
        except Exception as e:
            log.error("location reverse_geocode error: %s", e)
            return f"Fehler beim Reverse Geocoding: {e}"

    # ── Timezone ──────────────────────────────────────────────────────

    @capability("Ermittle die Zeitzone und aktuelle Uhrzeit für einen Ort oder eine IP-Adresse")
    async def get_timezone(self, location: str = "") -> str:
        """Liefere Zeitzone und aktuelle Ortszeit.

        Args:
            location: Stadtname, Ländercode ODER leer für eigene IP
        """
        loc = location.strip() if location else ""

        # Step 1: Resolve timezone string via IP lookup or geocoding
        tz_name = ""
        resolved_place = ""

        if not loc or _looks_like_ip(loc):
            # IP-based
            url = IPAPI_URL.format(ip=loc)
            try:
                async with httpx.AsyncClient(timeout=8) as client:
                    data = (await client.get(url, headers=_HEADERS)).json()
                if data.get("status") == "success":
                    tz_name        = data.get("timezone", "")
                    resolved_place = f"{data.get('city', '')}, {data.get('country', '')}".strip(", ")
            except Exception:
                pass
        else:
            # Geocode place name first, then extract timezone from ip-api via city search
            geo_url = NOMINATIM_GEO.format(q=loc.replace(" ", "+"))
            try:
                async with httpx.AsyncClient(timeout=8) as client:
                    geo = (await client.get(geo_url, headers=_HEADERS)).json()
                if geo:
                    lat = geo[0]["lat"]
                    lon = geo[0]["lon"]
                    resolved_place = geo[0].get("display_name", loc).split(",")[0]
                    # ip-api can return timezone for IP; use timezonedb-free alternative
                    # worldtimeapi by coordinates not supported; use geocoded city in ip-api
                    ip_url = f"http://ip-api.com/json/?fields=timezone&lat={lat}&lon={lon}"
                    tz_resp = (await client.get(ip_url, headers=_HEADERS)).json()
                    tz_name = tz_resp.get("timezone", "")
            except Exception:
                pass

        if not tz_name:
            return f"Zeitzone für \"{loc or 'eigene IP'}\" konnte nicht ermittelt werden."

        # Step 2: Fetch current local time from worldtimeapi.org
        try:
            async with httpx.AsyncClient(timeout=8) as client:
                wt = (await client.get(WORLDTIME_URL.format(tz=tz_name), headers=_HEADERS)).json()

            dt_raw    = wt.get("datetime", "")
            utc_off   = wt.get("utc_offset", "?")
            dst       = wt.get("dst", False)
            day_of_yr = wt.get("day_of_year", "?")
            week_num  = wt.get("week_number", "?")

            # Format datetime nicely: 2025-06-15T14:32:10.123456+02:00 → 15.06.2025 14:32:10
            if "T" in dt_raw:
                date_part, time_part = dt_raw.split("T", 1)
                time_clean = time_part[:8]  # HH:MM:SS
                y, m, d = date_part.split("-")
                local_time = f"{d}.{m}.{y} {time_clean}"
            else:
                local_time = dt_raw

            dst_str = " (Sommerzeit aktiv)" if dst else ""

            return (
                f"🕐 Zeitzone für {resolved_place or loc or 'eigene IP'}:\n"
                f"  Zeitzone:       {tz_name}\n"
                f"  Aktuelle Zeit:  {local_time}{dst_str}\n"
                f"  UTC-Offset:     {utc_off}\n"
                f"  Kalenderwoche:  KW {week_num}, Tag {day_of_yr} des Jahres"
            )

        except Exception as e:
            # worldtimeapi unavailable — return at least the timezone name
            return (
                f"🕐 Zeitzone für {resolved_place or loc or 'eigene IP'}:\n"
                f"  Zeitzone: {tz_name}\n"
                f"  (Aktuelle Uhrzeit konnte nicht abgerufen werden: {e})"
            )


# ── Utilities ─────────────────────────────────────────────────────────

def _cc_to_flag(cc: str) -> str:
    """Konvertiere zweistelligen Ländercode in Flaggen-Emoji."""
    cc = (cc or "").upper().strip()
    if len(cc) != 2 or not cc.isalpha():
        return "🌍"
    return chr(0x1F1E6 + ord(cc[0]) - ord("A")) + chr(0x1F1E6 + ord(cc[1]) - ord("A"))


def _looks_like_ip(s: str) -> bool:
    """Einfacher Check ob ein String wie eine IP-Adresse aussieht."""
    import re
    return bool(
        re.match(r"^\d{1,3}(\.\d{1,3}){3}$", s) or   # IPv4
        re.match(r"^[0-9a-fA-F:]{2,39}$", s)           # IPv6 (sehr vereinfacht)
    )


PLUGIN_CLASS = LocationPlugin
