"""BAN YARO — Gassi-Routen"""

import datetime as _dt
import json, math, os, uuid
import httpx
import polyline as _polyline
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from pydantic import BaseModel
from typing import Optional, List
from database import db
from auth import get_current_user, get_current_user_optional
from routes.achievements import update_streak, check_and_award
from timeutils import safe_client_time
from media_utils import convert_media
from routes.push import send_push_to_user

router = APIRouter()

ORS_API_KEY = os.getenv("ORS_API_KEY")

_MAX_AVG_KMH = 15.0  # Über diesem Wert wird die Route nicht für Stats/Trophäen gewertet

def _check_speed(distanz_km, dauer_min) -> bool:
    """True = gültig, False = zu schnell (wahrscheinlich motorisiert)."""
    if not distanz_km or not dauer_min or dauer_min <= 0:
        return True
    return (distanz_km / (dauer_min / 60)) <= _MAX_AVG_KMH


def _haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
    R  = 6_371_000
    p1 = math.radians(lat1)
    p2 = math.radians(lat2)
    dp = math.radians(lat2 - lat1)
    dl = math.radians(lon2 - lon1)
    a  = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2
    return 2 * R * math.asin(math.sqrt(a))


# ------------------------------------------------------------------
# Schemas
# ------------------------------------------------------------------
class GPSPoint(BaseModel):
    lat: float
    lon: float
    alt: Optional[float] = None

class RouteCreate(BaseModel):
    name:            str
    beschreibung:    Optional[str]         = None
    gps_track:       List[GPSPoint]
    distanz_km:      Optional[float]       = None
    dauer_min:       Optional[int]         = None
    schwierigkeit:   Optional[str]         = "leicht"   # leicht | mittel | anspruchsvoll
    untergrund:      Optional[str]         = None        # wald | asphalt | wiese | mix
    schatten:        Optional[bool]        = None
    leine_empfohlen: Optional[bool]        = None
    is_public:          Optional[bool]     = False
    hunde_tauglichkeit: Optional[str]      = None  # eingeschränkt | gut | sehr_gut | premium
    client_time:        Optional[str]      = None

class RouteUpdate(BaseModel):
    name:            Optional[str]  = None
    beschreibung:    Optional[str]  = None
    schwierigkeit:   Optional[str]  = None
    untergrund:      Optional[str]  = None
    schatten:        Optional[bool] = None
    leine_empfohlen: Optional[bool] = None
    is_public:          Optional[bool] = None
    hunde_tauglichkeit: Optional[str]  = None


def _simplify_track(track: list, max_pts: int = 40) -> list:
    """Reduziert GPS-Track auf max_pts Punkte für Vorschau."""
    if len(track) <= max_pts:
        return track
    step = len(track) / max_pts
    return [track[round(i * step)] for i in range(max_pts)]


def _parse(row) -> dict:
    d = dict(row)
    if isinstance(d.get('gps_track'), str):
        d['gps_track'] = json.loads(d['gps_track'])
    for k in ('schatten', 'leine_empfohlen', 'is_public'):
        if d.get(k) is not None:
            d[k] = bool(d[k])
    if isinstance(d.get('foto_urls'), str):
        d['foto_urls'] = json.loads(d['foto_urls'])
    return d


# ------------------------------------------------------------------
# GET /api/routes — Routen (optional: Umkreis vom Startpunkt)
# ------------------------------------------------------------------
@router.get("")
async def list_routes(
    lat:    Optional[float] = None,
    lon:    Optional[float] = None,
    radius: int             = 10000,
    user = Depends(get_current_user_optional),
):
    with db() as conn:
        rows = conn.execute("""
            SELECT r.id, r.user_id, r.name, r.beschreibung,
                   r.distanz_km, r.dauer_min, r.schwierigkeit,
                   r.untergrund, r.schatten, r.leine_empfohlen,
                   r.bewertung, r.anz_bewertungen, r.created_at,
                   r.gps_track, r.is_public, r.hunde_tauglichkeit, r.foto_urls,
                   u.name AS user_name,
                   json_extract(r.gps_track, '$[0].lat') AS start_lat,
                   json_extract(r.gps_track, '$[0].lon') AS start_lon,
                   json_extract(r.gps_track, '$[#-1].lat') AS end_lat,
                   json_extract(r.gps_track, '$[#-1].lon') AS end_lon
            FROM routes r
            LEFT JOIN users u ON u.id = r.user_id
            ORDER BY r.created_at DESC
        """).fetchall()

    result = []
    for row in rows:
        d = dict(row)
        for k in ('schatten', 'leine_empfohlen', 'is_public'):
            if d.get(k) is not None:
                d[k] = bool(d[k])
        if isinstance(d.get('foto_urls'), str):
            d['foto_urls'] = json.loads(d['foto_urls'])
        raw_track = json.loads(d.get('gps_track') or '[]')
        d['preview_track'] = _simplify_track(raw_track, 40)
        del d['gps_track']
        result.append(d)

    if lat is not None and lon is not None:
        result = [
            r for r in result
            if r['start_lat'] and _haversine(lat, lon, r['start_lat'], r['start_lon']) <= radius
        ]

    user_id = user['id'] if user else None
    result = [r for r in result if r.get('is_public', True) or r.get('user_id') == user_id]
    return result


# ------------------------------------------------------------------
# POST /api/routes — neue Route speichern (Login erforderlich)
# ------------------------------------------------------------------
@router.post("", status_code=201)
async def create_route(data: RouteCreate, user=Depends(get_current_user)):
    if len(data.gps_track) < 2:
        raise HTTPException(400, "GPS-Track braucht mindestens 2 Punkte.")

    gps_json  = json.dumps([p.model_dump() for p in data.gps_track])
    is_valid  = int(_check_speed(data.distanz_km, data.dauer_min))

    ct = safe_client_time(data.client_time)
    with db() as conn:
        cur = conn.execute("""
            INSERT INTO routes
                (user_id, name, beschreibung, gps_track, distanz_km, dauer_min,
                 schwierigkeit, untergrund, schatten, leine_empfohlen, is_public,
                 hunde_tauglichkeit, is_valid, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            user['id'], data.name, data.beschreibung, gps_json,
            data.distanz_km, data.dauer_min, data.schwierigkeit, data.untergrund,
            int(data.schatten)        if data.schatten        is not None else None,
            int(data.leine_empfohlen) if data.leine_empfohlen is not None else None,
            int(data.is_public) if data.is_public is not None else 1,
            data.hunde_tauglichkeit, is_valid, ct,
        ))
        row = conn.execute("SELECT * FROM routes WHERE id = ?", (cur.lastrowid,)).fetchone()
        update_streak(user['id'], conn)
        check_and_award(user['id'], conn)
    result = _parse(row)
    result['is_valid'] = bool(is_valid)
    return result


# ------------------------------------------------------------------
# POST /api/routes/suggest — Rundweg-Vorschlag via OpenRouteService
# ------------------------------------------------------------------
class SuggestRequest(BaseModel):
    lat:         float
    lon:         float
    distance_km: float   # Zieldistanz in km (z.B. 2.0, 4.0, 6.0)
    seed:        int = 0  # 0-4: verschiedene Routenvarianten

WEEKLY_LIMIT = 20

@router.post("/suggest")
async def suggest_route(data: SuggestRequest, user=Depends(get_current_user)):
    if not (0.5 <= data.distance_km <= 15):
        raise HTTPException(400, "distance_km muss zwischen 0.5 und 15 liegen.")

    if not ORS_API_KEY:
        raise HTTPException(503, "ORS nicht konfiguriert")

    is_privileged = (
        user.get("rolle") in ("admin", "moderator") or
        user.get("is_moderator") or
        user.get("is_social_media") or
        user.get("breeder_status") == "approved"
    )

    today = _dt.date.today()
    week_start = (today - _dt.timedelta(days=today.weekday())).isoformat()

    if not is_privileged:
        with db() as conn:
            row = conn.execute(
                "SELECT count FROM route_suggest_usage WHERE user_id=? AND week=?",
                (user["id"], week_start)
            ).fetchone()
            current_count = row["count"] if row else 0

        if current_count >= WEEKLY_LIMIT:
            raise HTTPException(429, f"Wochenlimit von {WEEKLY_LIMIT} Routenvorschlägen erreicht.")

    payload = {
        "coordinates": [[data.lon, data.lat]],
        "options": {
            "round_trip": {
                "length": data.distance_km * 1000,
                "points": 5,
                "seed":   data.seed,
            },
            "avoid_features": ["ferries", "steps"],
        },
        "units":        "m",
        "geometry":     True,
        "instructions": False,
    }

    try:
        async with httpx.AsyncClient(timeout=10) as client:
            resp = await client.post(
                "https://api.openrouteservice.org/v2/directions/foot-walking",
                headers={
                    "Authorization": f"Bearer {ORS_API_KEY}",
                    "Content-Type":  "application/json",
                },
                json=payload,
            )
    except httpx.TimeoutException:
        raise HTTPException(504, "ORS-Anfrage hat das Zeitlimit überschritten.")

    if resp.status_code != 200:
        try:
            detail = resp.json()
        except Exception:
            detail = resp.text
        raise HTTPException(502, f"ORS-Fehler: {detail}")

    body = resp.json()
    try:
        route      = body["routes"][0]
        geometry   = route["geometry"]
        summary    = route["summary"]
        distanz_m  = summary.get("distance", data.distance_km * 1000)
        dauer_s    = summary.get("duration", 0)
    except (KeyError, IndexError) as exc:
        raise HTTPException(502, f"Unerwartete ORS-Antwort: {exc}")

    # encoded polyline → [[lat, lon], ...]
    points = _polyline.decode(geometry)
    gps_track = [{"lat": p[0], "lon": p[1]} for p in points]

    distanz_km = round(distanz_m / 1000, 2)
    dauer_min  = max(1, round(dauer_s / 60))

    if distanz_km < 3:
        schwierigkeit = "leicht"
    elif distanz_km <= 5:
        schwierigkeit = "mittel"
    else:
        schwierigkeit = "anspruchsvoll"

    if not is_privileged:
        with db() as conn:
            conn.execute("""
                INSERT INTO route_suggest_usage (user_id, week, count) VALUES (?, ?, 1)
                ON CONFLICT(user_id, week) DO UPDATE SET count = count + 1
            """, (user["id"], week_start))
        current_count += 1

    # Täglichen Gesamtzähler hochzählen (für Admin-Stats)
    today_str = _dt.date.today().isoformat()
    with db() as conn:
        conn.execute("""
            INSERT INTO ors_daily_total (date, count) VALUES (?, 1)
            ON CONFLICT(date) DO UPDATE SET count = count + 1
        """, (today_str,))

    weekly_remaining = None if is_privileged else max(0, WEEKLY_LIMIT - current_count)

    return {
        "name":             f"Rundweg {distanz_km:.0f} km",
        "gps_track":        gps_track,
        "distanz_km":       distanz_km,
        "dauer_min":        dauer_min,
        "schwierigkeit":    schwierigkeit,
        "weekly_remaining": weekly_remaining,
    }


# ------------------------------------------------------------------
# GET /api/routes/{id} — Route mit vollem GPS-Track
# ------------------------------------------------------------------
@router.get("/{route_id}")
async def get_route(route_id: int):
    with db() as conn:
        row = conn.execute(
            "SELECT r.*, u.name AS user_name FROM routes r LEFT JOIN users u ON u.id = r.user_id WHERE r.id = ?",
            (route_id,)
        ).fetchone()
    if not row:
        raise HTTPException(404, "Route nicht gefunden.")
    return _parse(row)


# ------------------------------------------------------------------
# PATCH /api/routes/{id}
# ------------------------------------------------------------------
@router.patch("/{route_id}")
async def update_route(route_id: int, data: RouteUpdate, user=Depends(get_current_user)):
    with db() as conn:
        row = conn.execute("SELECT * FROM routes WHERE id = ?", (route_id,)).fetchone()
        if not row:
            raise HTTPException(404, "Route nicht gefunden.")
        if row['user_id'] != user['id']:
            raise HTTPException(403, "Nicht berechtigt.")

        updates = data.model_dump(exclude_none=True)
        if updates:
            for key in ('schatten', 'leine_empfohlen', 'is_public'):
                if key in updates:
                    updates[key] = int(updates[key])
            cols = ', '.join(f"{k} = ?" for k in updates)
            conn.execute(f"UPDATE routes SET {cols} WHERE id = ?", [*updates.values(), route_id])

        row = conn.execute("SELECT * FROM routes WHERE id = ?", (route_id,)).fetchone()
    return _parse(row)


# ------------------------------------------------------------------
# PATCH /api/routes/{id}/trim — Route kürzen (Datenschutz)
# ------------------------------------------------------------------
class RouteTrim(BaseModel):
    gps_track: List[GPSPoint]

@router.patch("/{route_id}/trim")
async def trim_route(route_id: int, data: RouteTrim, user=Depends(get_current_user)):
    if len(data.gps_track) < 2:
        raise HTTPException(400, "Mindestens 2 GPS-Punkte erforderlich.")

    with db() as conn:
        row = conn.execute("SELECT * FROM routes WHERE id=?", (route_id,)).fetchone()
        if not row:
            raise HTTPException(404, "Route nicht gefunden.")
        if row['user_id'] != user['id']:
            raise HTTPException(403, "Nicht berechtigt.")

        # Original-Werte beim ersten Kürzen einmalig sichern
        if row['original_km'] is None:
            conn.execute(
                "UPDATE routes SET original_km=?, original_dauer_min=? WHERE id=?",
                (row['distanz_km'], row['dauer_min'], route_id)
            )
            orig_km  = row['distanz_km'] or 0
            orig_min = row['dauer_min']  or 0
        else:
            orig_km  = row['original_km']
            orig_min = row['original_dauer_min'] or 0

        # Neue Distanz berechnen
        new_track = [p.model_dump() for p in data.gps_track]
        new_km = 0.0
        for i in range(1, len(new_track)):
            p1, p2 = new_track[i-1], new_track[i]
            dlat = math.radians(p2['lat'] - p1['lat'])
            dlon = math.radians(p2['lon'] - p1['lon'])
            a = math.sin(dlat/2)**2 + math.cos(math.radians(p1['lat'])) * math.cos(math.radians(p2['lat'])) * math.sin(dlon/2)**2
            new_km += 6371 * 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
        new_km = round(new_km, 2)

        # Dauer proportional schätzen (Original-Pace)
        pace = orig_min / orig_km if orig_km > 0 else 10
        new_min = max(1, round(new_km * pace))

        conn.execute(
            "UPDATE routes SET gps_track=?, distanz_km=?, dauer_min=? WHERE id=?",
            (json.dumps(new_track), new_km, new_min, route_id)
        )
        row = conn.execute("SELECT * FROM routes WHERE id=?", (route_id,)).fetchone()
    return _parse(row)


# ------------------------------------------------------------------
# DELETE /api/routes/{id}
# ------------------------------------------------------------------
@router.delete("/{route_id}", status_code=204)
async def delete_route(route_id: int, user=Depends(get_current_user)):
    with db() as conn:
        row = conn.execute("SELECT * FROM routes WHERE id = ?", (route_id,)).fetchone()
        if not row:
            raise HTTPException(404, "Route nicht gefunden.")
        if row['user_id'] != user['id']:
            raise HTTPException(403, "Nicht berechtigt.")
        conn.execute("DELETE FROM routes WHERE id = ?", (route_id,))


# ------------------------------------------------------------------
# POST /api/routes/{id}/rate — Bewertung abgeben
# ------------------------------------------------------------------
class RouteRate(BaseModel):
    wertung: float  # 1–5

@router.post("/{route_id}/rate")
async def rate_route(route_id: int, data: RouteRate, user=Depends(get_current_user)):
    if not 1 <= data.wertung <= 5:
        raise HTTPException(400, "Wertung muss zwischen 1 und 5 liegen.")
    with db() as conn:
        row = conn.execute("SELECT * FROM routes WHERE id = ?", (route_id,)).fetchone()
        if not row:
            raise HTTPException(404, "Route nicht gefunden.")
        r = dict(row)
        n = (r['anz_bewertungen'] or 0) + 1
        avg = ((r['bewertung'] or 0) * (n - 1) + data.wertung) / n
        conn.execute(
            "UPDATE routes SET bewertung=?, anz_bewertungen=? WHERE id=?",
            (round(avg, 2), n, route_id)
        )
    return {'bewertung': round(avg, 2), 'anz_bewertungen': n}


# ------------------------------------------------------------------
# POST /api/routes/{id}/walked — Gelaufene km ins Profil eintragen
# ------------------------------------------------------------------
class WalkRecord(BaseModel):
    walked_km:    float
    progress_pct: int

@router.post("/{route_id}/walked", status_code=201)
async def record_walk(route_id: int, body: WalkRecord, user=Depends(get_current_user)):
    if body.progress_pct < 50:
        raise HTTPException(400, "Mindestens 50 % der Route müssen absolviert sein.")
    uid = user["id"]
    with db() as conn:
        conn.execute(
            "INSERT INTO route_walks (user_id, route_id, walked_km) VALUES (?,?,?)",
            (uid, route_id, round(max(0.01, body.walked_km), 2))
        )
        update_streak(uid, conn)
        new_badges = check_and_award(uid, conn)
    return {"ok": True, "new_badges": new_badges}


# ------------------------------------------------------------------
# POST /api/routes/{id}/reverse — GPS-Track umkehren
# ------------------------------------------------------------------
@router.post("/{route_id}/reverse", status_code=200)
async def reverse_route(route_id: int, user=Depends(get_current_user)):
    uid = user["id"]
    with db() as conn:
        row = conn.execute("SELECT user_id, gps_track FROM routes WHERE id=?", (route_id,)).fetchone()
        if not row:
            raise HTTPException(404, "Route nicht gefunden.")
        if row["user_id"] != uid:
            raise HTTPException(403, "Nur der Ersteller kann die Route umkehren.")
        track = json.loads(row["gps_track"])
        track.reverse()
        conn.execute("UPDATE routes SET gps_track=? WHERE id=?", (json.dumps(track), route_id))
    return {"ok": True}


# ------------------------------------------------------------------
# POST /api/routes/{id}/photo — Foto hochladen
# ------------------------------------------------------------------
MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")

@router.post("/{route_id}/photo", status_code=201)
async def add_route_photo(
    route_id: int,
    file: UploadFile = File(...),
    user = Depends(get_current_user),
):
    with db() as conn:
        row = conn.execute("SELECT * FROM routes WHERE id=?", (route_id,)).fetchone()
    if not row:
        raise HTTPException(404, "Route nicht gefunden.")
    if dict(row)['user_id'] != user['id']:
        raise HTTPException(403, "Nicht berechtigt.")

    data, ext = convert_media(await file.read(), file.filename or "")
    if not ext:
        ext = ".jpg"
    filename = f"route_{route_id}_{uuid.uuid4().hex[:8]}{ext}"
    path     = os.path.join(MEDIA_DIR, "routes", filename)
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "wb") as f:
        f.write(data)

    foto_url = f"/media/routes/{filename}"
    with db() as conn:
        row = conn.execute("SELECT foto_urls FROM routes WHERE id=?", (route_id,)).fetchone()
        urls = json.loads(dict(row)['foto_urls'] or '[]')
        urls.append(foto_url)
        conn.execute("UPDATE routes SET foto_urls=? WHERE id=?", (json.dumps(urls), route_id))
    return {'foto_url': foto_url, 'foto_urls': urls}


# ------------------------------------------------------------------
# POST /api/routes/{id}/feedback — Feedback an Route-Ersteller
# ------------------------------------------------------------------
class RouteFeedback(BaseModel):
    text: str

@router.post("/{route_id}/feedback", status_code=201)
async def route_feedback(route_id: int, data: RouteFeedback, user=Depends(get_current_user)):
    if len(data.text.strip()) < 5:
        raise HTTPException(400, "Feedback zu kurz.")
    with db() as conn:
        row = conn.execute(
            "SELECT user_id, name FROM routes WHERE id=?", (route_id,)
        ).fetchone()
    if not row:
        raise HTTPException(404, "Route nicht gefunden.")
    if row["user_id"] == user["id"]:
        raise HTTPException(400, "Eigene Route kann nicht bewertet werden.")

    send_push_to_user(row["user_id"], {
        "type":    "route_feedback",
        "title":   "📍 Feedback zu \u201e" + row['name'] + "\u201c",
        "body":    data.text.strip()[:120],
        "route_id": route_id,
    })
    return {"ok": True}


# ------------------------------------------------------------------
# GET /api/routes/{id}/elevation — Höhenprofil via OpenTopoData
# ------------------------------------------------------------------
@router.get("/{route_id}/elevation")
async def route_elevation(route_id: int, _user=Depends(get_current_user_optional)):
    with db() as conn:
        row = conn.execute("SELECT gps_track FROM routes WHERE id=?", (route_id,)).fetchone()
    if not row:
        raise HTTPException(404)

    track = json.loads(row["gps_track"] or "[]")
    if not track:
        return {"elevations": []}

    # Bereits mit Höhe gespeichert?
    if all(p.get("alt") is not None for p in track):
        return {"elevations": [{"lat": p["lat"], "lon": p["lon"], "alt": p["alt"]} for p in track]}

    # Auf max. 60 Punkte reduzieren
    step = max(1, len(track) // 60)
    sample = track[::step]
    if track[-1] not in sample:
        sample.append(track[-1])

    locations = "|".join(f"{p['lat']},{p['lon']}" for p in sample)
    try:
        async with httpx.AsyncClient(timeout=8) as client:
            r = await client.get(
                f"https://api.opentopodata.org/v1/srtm90m?locations={locations}"
            )
            results = r.json().get("results", [])
        return {"elevations": [
            {"lat": res["location"]["lat"], "lon": res["location"]["lng"], "alt": res.get("elevation", 0)}
            for res in results
        ]}
    except Exception:
        return {"elevations": []}
