"""Garmin Connect endpoints (/api/garmin/*)."""
from __future__ import annotations

import json

from fastapi import APIRouter, Cookie, HTTPException, Request
from fastapi.responses import JSONResponse, StreamingResponse

from bincio.serve import deps, tasks

router = APIRouter()


def _garmin_user_message(exc: Exception) -> str:
    """Return a human-friendly error message for common Garmin login failures."""
    msg = str(exc)
    fallback = (
        " In the meantime, you can export your activities from Garmin Connect "
        "(garmin.com → Activities → Export) or Garmin Express as FIT files "
        "and upload them directly."
    )
    if "429" in msg or "rate limit" in msg.lower():
        return (
            "Garmin is rate-limiting this server's IP address (HTTP 429). "
            "Wait a few hours and try again." + fallback
        )
    if "403" in msg:
        return (
            "Cloudflare is blocking the login request (HTTP 403). "
            "This is a known upstream issue — try again later or update garminconnect "
            "(uv sync --extra garmin)." + fallback
        )
    if "GARMIN Authentication Application" in msg or "unexpected title" in msg.lower():
        return (
            "Garmin's login page returned a CAPTCHA or MFA challenge that "
            "cannot be completed automatically. Try again later, or disable "
            "two-factor authentication on your Garmin account." + fallback
        )
    return f"Login failed: {exc}" + fallback


@router.get("/api/garmin/status")
async def garmin_status(bincio_session: str | None = Cookie(default=None)) -> JSONResponse:
    """Return whether Garmin credentials are stored for the current user."""
    user = deps._require_user(bincio_session)
    dd = deps._get_data_dir() / user.handle
    from bincio.extract.garmin_api import has_credentials
    from bincio.extract.garmin_sync import _load_sync_state
    connected = has_credentials(dd)
    last_sync = None
    if connected:
        state = _load_sync_state(dd)
        last_sync = state.get("last_sync_at")
    return JSONResponse({"connected": connected, "last_sync": last_sync})


@router.post("/api/garmin/connect")
async def garmin_connect(
    request: Request,
    bincio_session: str | None = Cookie(default=None),
) -> JSONResponse:
    """Test Garmin login with the supplied credentials and save them on success."""
    user = deps._require_user(bincio_session)
    body = await request.json()
    email    = (body.get("email") or "").strip()
    password = body.get("password") or ""
    if not email or not password:
        raise HTTPException(400, "email and password are required")

    data_dir = deps._get_data_dir()
    user_dir = data_dir / user.handle
    from bincio.extract.garmin_api import GarminError, test_login
    try:
        info = test_login(data_dir, user_dir, email, password)
    except GarminError as exc:
        raise HTTPException(400, _garmin_user_message(exc))
    return JSONResponse({"ok": True, **info})


@router.post("/api/garmin/disconnect")
async def garmin_disconnect(bincio_session: str | None = Cookie(default=None)) -> JSONResponse:
    """Remove stored Garmin credentials and session for the current user."""
    user = deps._require_user(bincio_session)
    dd = deps._get_data_dir() / user.handle
    from bincio.extract.garmin_api import delete_credentials
    delete_credentials(dd)
    return JSONResponse({"ok": True})


@router.get("/api/garmin/sync/stream")
async def garmin_sync_stream(bincio_session: str | None = Cookie(default=None)) -> StreamingResponse:
    """SSE endpoint — streams per-activity Garmin sync progress."""
    user = deps._require_user(bincio_session)
    data_dir = deps._get_data_dir()
    user_dir = data_dir / user.handle

    from bincio.extract.garmin_api import GarminError, has_credentials
    if not has_credentials(user_dir):
        raise HTTPException(400, "No Garmin credentials stored — connect first")

    from bincio.extract.garmin_sync import garmin_sync_iter

    def event_stream():
        try:
            for event in garmin_sync_iter(data_dir, user_dir):
                if event["type"] == "done":
                    tasks._trigger_rebuild(user.handle)
                yield f"data: {json.dumps(event)}\n\n"
        except GarminError as exc:
            yield f"data: {json.dumps({'type': 'error', 'message': _garmin_user_message(exc)})}\n\n"
        except Exception as exc:
            yield f"data: {json.dumps({'type': 'error', 'message': _garmin_user_message(exc)})}\n\n"

    return StreamingResponse(
        event_stream(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )


@router.post("/api/garmin/import-gear")
async def garmin_import_gear(bincio_session: str | None = Cookie(default=None)) -> JSONResponse:
    """One-time backfill: fetch gear registry from Garmin and match to existing activities by timestamp."""
    from bincio.extract.garmin_api import GarminError, has_credentials
    from bincio.extract.garmin_sync import import_garmin_gear

    user = deps._require_user(bincio_session)
    data_dir = deps._get_data_dir()
    user_dir = data_dir / user.handle

    if not has_credentials(user_dir):
        raise HTTPException(400, "No Garmin credentials stored — connect first")

    try:
        result = import_garmin_gear(data_dir, user_dir)
    except GarminError as exc:
        raise HTTPException(502, _garmin_user_message(exc))

    tasks._trigger_rebuild(user.handle)
    return JSONResponse({"ok": True, **result})
