#!/usr/bin/env python3
"""CLI coordinate parser: extracts lat/lon from Apple Maps, amap, or raw text."""

import sys
import re
import math
import urllib.request
import urllib.error
import urllib.parse
import requests

USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/27.0 Mobile/24A5370h Safari/604.1"

GCJ_A = 6378245.0
GCJ_EE = 0.00669342162296594323


def safe_decode(s):
    """URL-decode, replace + with space, return '' on error."""
    if not s:
        return ""
    try:
        return re.sub(r'\+', ' ', urllib.parse.unquote(str(s)))
    except Exception:
        return str(s)


def extract_from_string(s):
    """Extract lat/lon/name/src from a string. Returns dict or None."""
    if not s:
        return None
    string = str(s)

    # Apple Maps: coordinate=, ll=, sll=  (lat, lon order)
    m = re.search(r'(?:coordinate|ll|sll)=(-?\d{1,3}\.\d+)(?:,|%2C)(-?\d{1,3}\.\d+)', string, re.I)
    if m:
        nm = re.search(r'[?&]name=([^&]+)', string, re.I)
        name = safe_decode(nm.group(1)) if nm else ""
        return {"lat": float(m.group(1)), "lon": float(m.group(2)), "name": name, "src": "apple"}

    # Amap ?p= pattern: lon,lat order in URL!
    m = re.search(
        r'[?&]p=[^,&%]*(?:,|%2C)(-?\d{1,3}\.\d+)(?:,|%2C)(-?\d{1,3}\.\d+)(?:(?:,|%2C)((?:(?!,|%2C|&).)+))?',
        string, re.I)
    if m:
        name = safe_decode(m.group(3)) if m.group(3) else ""
        return {"lat": float(m.group(2)), "lon": float(m.group(1)), "name": name, "src": "amap"}

    # Amap ?q= pattern: lon,lat order
    m = re.search(
        r'[?&]q=(-?\d{1,3}\.\d+)(?:,|%2C)(-?\d{1,3}\.\d+)(?:(?:,|%2C)((?:(?!,|%2C|&).)+))?',
        string, re.I)
    if m:
        name = safe_decode(m.group(3)) if m.group(3) else ""
        return {"lat": float(m.group(2)), "lon": float(m.group(1)), "name": name, "src": "amap"}

    # Raw lat,lon text: lat first
    m = re.search(r'(-?\d{1,3}\.\d{4,})\s*(?:,|%2C)\s*(-?\d{1,3}\.\d{4,})', string)
    if m:
        return {"lat": float(m.group(1)), "lon": float(m.group(2)), "name": "", "src": "text"}

    return None


def gcj_out_of_china(lng, lat):
    """Return True if coordinates are outside China."""
    return lng < 72.004 or lng > 137.8347 or lat < 0.8293 or lat > 55.8271


def gcj_delta_lat(x, y):
    r = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x))
    r += ((20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0) / 3.0
    r += ((20.0 * math.sin(y * math.pi) + 40.0 * math.sin((y / 3.0) * math.pi)) * 2.0) / 3.0
    r += ((160.0 * math.sin((y / 12.0) * math.pi) + 320 * math.sin((y * math.pi) / 30.0)) * 2.0) / 3.0
    return r


def gcj_delta_lon(x, y):
    r = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x))
    r += ((20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0) / 3.0
    r += ((20.0 * math.sin(x * math.pi) + 40.0 * math.sin((x / 3.0) * math.pi)) * 2.0) / 3.0
    r += ((150.0 * math.sin((x / 12.0) * math.pi) + 300.0 * math.sin((x / 30.0) * math.pi)) * 2.0) / 3.0
    return r


def wgs84_to_gcj02(lat, lon):
    """WGS84 -> GCJ-02 forward transform."""
    if gcj_out_of_china(lon, lat):
        return {"lat": lat, "lon": lon}
    d_lat = gcj_delta_lat(lon - 105.0, lat - 35.0)
    d_lon = gcj_delta_lon(lon - 105.0, lat - 35.0)
    rad_lat = (lat / 180.0) * math.pi
    magic = math.sin(rad_lat)
    magic = 1 - GCJ_EE * magic * magic
    sqrt_magic = math.sqrt(magic)
    d_lat = (d_lat * 180.0) / (((GCJ_A * (1 - GCJ_EE)) / (magic * sqrt_magic)) * math.pi)
    d_lon = (d_lon * 180.0) / ((GCJ_A / sqrt_magic) * math.cos(rad_lat) * math.pi)
    return {"lat": lat + d_lat, "lon": lon + d_lon}


def gcj02_to_wgs84(lat, lon):
    """GCJ-02 -> WGS84 iterative inverse (6 iterations)."""
    if gcj_out_of_china(lon, lat):
        return {"lat": lat, "lon": lon}
    wgs_lat = lat
    wgs_lon = lon
    for _ in range(6):
        g = wgs84_to_gcj02(wgs_lat, wgs_lon)
        err_lat = g["lat"] - lat
        err_lon = g["lon"] - lon
        if abs(err_lat) < 1e-9 and abs(err_lon) < 1e-9:
            break
        wgs_lat -= err_lat
        wgs_lon -= err_lon
    return {"lat": wgs_lat, "lon": wgs_lon}


def round6(n):
    """Round to 6 decimal places."""
    return round(float(n), 6)


def fetch_url(url, headers=None):
    """Fetch URL with manual redirect handling. Returns (response_url, body, location_header)."""
    req = urllib.request.Request(url, headers=headers or {
        "User-Agent": USER_AGENT,
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Language": "zh-CN,zh-Hans;q=0.9",
    })
    try:
        resp = urllib.request.urlopen(req, timeout=10)
        return resp.url, resp.read().decode("utf-8", errors="replace"), resp.headers.get("Location")
    except urllib.error.HTTPError as e:
        if e.code in (301, 302, 303, 307, 308) and e.headers.get("Location"):
            return e.url, "", e.headers.get("Location")
        raise


def parse_coords(raw):
    """Parse raw text, extract URL, fetch and follow redirects, extract coordinates."""
    text = str(raw or "").strip()
    if not text:
        raise ValueError("Empty input")

    url_match = re.search(r'https?://[^\s\'"<>]+', text, re.I)
    target = url_match.group(0) if url_match else text

    # Try extraction on target first
    hit = extract_from_string(target)
    if hit:
        return hit

    if url_match:
        cur = target
        for _ in range(5):
            resp_url, body, location = fetch_url(cur)
            # Try location header
            if location:
                cur = urllib.parse.urljoin(cur, location)
                hit = extract_from_string(cur)
                if hit:
                    return hit
                continue
            # Try response URL
            hit = extract_from_string(resp_url)
            if hit:
                return hit
            # Try body
            if body:
                hit = extract_from_string(body)
                if hit:
                    return hit
            break

    raise ValueError("Failed to extract coordinates from URL")


def main():
    if len(sys.argv) < 2:
        print("Usage: python wloc.py <text with URL>", file=sys.stderr)
        sys.exit(1)

    raw = sys.argv[1]
    try:
        result = parse_coords(raw)
        lat = result["lat"]
        lon = result["lon"]
        src = result.get("src", "")

        # Convert GCJ-02 to WGS84 for amap and apple sources
        if src in ("amap", "apple"):
            wgs = gcj02_to_wgs84(lat, lon)
            lat = wgs["lat"]
            lon = wgs["lon"]

        lat = round6(lat)
        lon = round6(lon)
        print(f"lat={lat} lon={lon}")
        with open("./result.txt", "w") as file:
        	file.write(f"{lat},{lon}")
        
        return lat, lon
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
