"""Gear registry endpoints (/api/gear)."""
from __future__ import annotations

import json
import uuid
from pathlib import Path

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

from bincio.serve import deps

router = APIRouter()

_GEAR_TYPES = {"bike", "shoes", "skis", "other"}


def _gear_path(user_dir: Path) -> Path:
    return user_dir / "gear.json"


def _load(user_dir: Path) -> list[dict]:
    p = _gear_path(user_dir)
    if not p.exists():
        return []
    try:
        data = json.loads(p.read_text(encoding="utf-8"))
        return data.get("items", [])
    except (OSError, json.JSONDecodeError):
        return []


def _save(user_dir: Path, items: list[dict]) -> None:
    _gear_path(user_dir).write_text(
        json.dumps({"items": items}, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )


@router.get("/api/gear")
async def gear_list(bincio_session: str | None = Cookie(default=None)) -> JSONResponse:
    user = deps._require_user(bincio_session)
    items = _load(deps._get_data_dir() / user.handle)
    return JSONResponse({"items": items})


@router.post("/api/gear")
async def gear_add(
    request: Request,
    bincio_session: str | None = Cookie(default=None),
) -> JSONResponse:
    user = deps._require_user(bincio_session)
    body = await request.json()
    name = str(body.get("name", "")).strip()
    if not name:
        raise HTTPException(400, "name is required")
    gear_type = str(body.get("type", "other")).strip()
    if gear_type not in _GEAR_TYPES:
        raise HTTPException(400, f"type must be one of: {', '.join(sorted(_GEAR_TYPES))}")
    strava_id = str(body.get("strava_id", "")).strip() or None
    weight_g = body.get("weight_g")
    if weight_g is not None:
        try:
            weight_g = int(weight_g)
            if weight_g < 0:
                raise ValueError
        except (TypeError, ValueError):
            raise HTTPException(400, "weight_g must be a non-negative integer (grams)")

    user_dir = deps._get_data_dir() / user.handle
    items = _load(user_dir)

    # Deduplicate by strava_id if provided
    if strava_id and any(i.get("strava_id") == strava_id for i in items):
        existing = next(i for i in items if i.get("strava_id") == strava_id)
        return JSONResponse({"ok": True, "item": existing, "created": False})

    item: dict = {
        "id": str(uuid.uuid4()),
        "name": name,
        "type": gear_type,
        "retired": False,
    }
    if strava_id:
        item["strava_id"] = strava_id
    if weight_g is not None:
        item["weight_g"] = weight_g

    items.append(item)
    _save(user_dir, items)
    return JSONResponse({"ok": True, "item": item, "created": True}, status_code=201)


@router.patch("/api/gear/{item_id}")
async def gear_update(
    item_id: str,
    request: Request,
    bincio_session: str | None = Cookie(default=None),
) -> JSONResponse:
    user = deps._require_user(bincio_session)
    user_dir = deps._get_data_dir() / user.handle
    items = _load(user_dir)

    idx = next((i for i, g in enumerate(items) if g["id"] == item_id), None)
    if idx is None:
        raise HTTPException(404, "Gear item not found")

    body = await request.json()
    item = dict(items[idx])

    if "name" in body:
        name = str(body["name"]).strip()
        if not name:
            raise HTTPException(400, "name cannot be empty")
        item["name"] = name
    if "type" in body:
        gear_type = str(body["type"]).strip()
        if gear_type not in _GEAR_TYPES:
            raise HTTPException(400, f"type must be one of: {', '.join(sorted(_GEAR_TYPES))}")
        item["type"] = gear_type
    if "retired" in body:
        item["retired"] = bool(body["retired"])
    if "weight_g" in body:
        w = body["weight_g"]
        if w is None:
            item.pop("weight_g", None)
        else:
            try:
                w = int(w)
                if w < 0:
                    raise ValueError
            except (TypeError, ValueError):
                raise HTTPException(400, "weight_g must be a non-negative integer (grams)")
            item["weight_g"] = w

    items[idx] = item
    _save(user_dir, items)
    return JSONResponse({"ok": True, "item": item})


@router.delete("/api/gear/{item_id}")
async def gear_delete(
    item_id: str,
    bincio_session: str | None = Cookie(default=None),
) -> JSONResponse:
    user = deps._require_user(bincio_session)
    user_dir = deps._get_data_dir() / user.handle
    items = _load(user_dir)

    before = len(items)
    items = [g for g in items if g["id"] != item_id]
    if len(items) == before:
        raise HTTPException(404, "Gear item not found")

    _save(user_dir, items)
    return JSONResponse({"ok": True})


# ── Parts ─────────────────────────────────────────────────────────────────────

def _find_item(items: list[dict], item_id: str) -> tuple[int, dict]:
    idx = next((i for i, g in enumerate(items) if g["id"] == item_id), None)
    if idx is None:
        raise HTTPException(404, "Gear item not found")
    return idx, items[idx]


@router.post("/api/gear/{item_id}/parts")
async def part_add(
    item_id: str,
    request: Request,
    bincio_session: str | None = Cookie(default=None),
) -> JSONResponse:
    user = deps._require_user(bincio_session)
    user_dir = deps._get_data_dir() / user.handle
    items = _load(user_dir)
    idx, item = _find_item(items, item_id)

    body = await request.json()
    name = str(body.get("name", "")).strip()
    if not name:
        raise HTTPException(400, "name is required")
    threshold_km = body.get("threshold_km")
    if threshold_km is not None:
        try:
            threshold_km = float(threshold_km)
        except (TypeError, ValueError):
            raise HTTPException(400, "threshold_km must be a number")

    part: dict = {"id": str(uuid.uuid4()), "name": name, "replacements": []}
    if threshold_km is not None:
        part["threshold_km"] = threshold_km

    item = dict(item)
    item.setdefault("parts", [])
    item["parts"] = [*item["parts"], part]
    items[idx] = item
    _save(user_dir, items)
    return JSONResponse({"ok": True, "part": part}, status_code=201)


@router.patch("/api/gear/{item_id}/parts/{part_id}")
async def part_update(
    item_id: str,
    part_id: str,
    request: Request,
    bincio_session: str | None = Cookie(default=None),
) -> JSONResponse:
    user = deps._require_user(bincio_session)
    user_dir = deps._get_data_dir() / user.handle
    items = _load(user_dir)
    idx, item = _find_item(items, item_id)

    parts = list(item.get("parts", []))
    pidx = next((i for i, p in enumerate(parts) if p["id"] == part_id), None)
    if pidx is None:
        raise HTTPException(404, "Part not found")

    body = await request.json()
    part = dict(parts[pidx])
    if "name" in body:
        name = str(body["name"]).strip()
        if not name:
            raise HTTPException(400, "name cannot be empty")
        part["name"] = name
    if "threshold_km" in body:
        try:
            part["threshold_km"] = float(body["threshold_km"])
        except (TypeError, ValueError):
            raise HTTPException(400, "threshold_km must be a number")

    parts[pidx] = part
    item = {**item, "parts": parts}
    items[idx] = item
    _save(user_dir, items)
    return JSONResponse({"ok": True, "part": part})


@router.delete("/api/gear/{item_id}/parts/{part_id}")
async def part_delete(
    item_id: str,
    part_id: str,
    bincio_session: str | None = Cookie(default=None),
) -> JSONResponse:
    user = deps._require_user(bincio_session)
    user_dir = deps._get_data_dir() / user.handle
    items = _load(user_dir)
    idx, item = _find_item(items, item_id)

    parts = [p for p in item.get("parts", []) if p["id"] != part_id]
    items[idx] = {**item, "parts": parts}
    _save(user_dir, items)
    return JSONResponse({"ok": True})


@router.post("/api/gear/{item_id}/parts/{part_id}/replacements")
async def replacement_add(
    item_id: str,
    part_id: str,
    request: Request,
    bincio_session: str | None = Cookie(default=None),
) -> JSONResponse:
    """Log a replacement event for a part. date defaults to today (UTC)."""
    from datetime import UTC, datetime

    user = deps._require_user(bincio_session)
    user_dir = deps._get_data_dir() / user.handle
    items = _load(user_dir)
    idx, item = _find_item(items, item_id)

    parts = list(item.get("parts", []))
    pidx = next((i for i, p in enumerate(parts) if p["id"] == part_id), None)
    if pidx is None:
        raise HTTPException(404, "Part not found")

    body = await request.json()
    date = str(body.get("date", "")).strip() or datetime.now(UTC).strftime("%Y-%m-%d")
    note = str(body.get("note", "")).strip() or None

    entry: dict = {"id": str(uuid.uuid4()), "date": date}
    if note:
        entry["note"] = note

    part = dict(parts[pidx])
    part["replacements"] = [*part.get("replacements", []), entry]
    parts[pidx] = part
    items[idx] = {**item, "parts": parts}
    _save(user_dir, items)
    return JSONResponse({"ok": True, "replacement": entry}, status_code=201)


@router.delete("/api/gear/{item_id}/parts/{part_id}/replacements/{replacement_id}")
async def replacement_delete(
    item_id: str,
    part_id: str,
    replacement_id: str,
    bincio_session: str | None = Cookie(default=None),
) -> JSONResponse:
    user = deps._require_user(bincio_session)
    user_dir = deps._get_data_dir() / user.handle
    items = _load(user_dir)
    idx, item = _find_item(items, item_id)

    parts = list(item.get("parts", []))
    pidx = next((i for i, p in enumerate(parts) if p["id"] == part_id), None)
    if pidx is None:
        raise HTTPException(404, "Part not found")

    part = dict(parts[pidx])
    part["replacements"] = [r for r in part.get("replacements", []) if r["id"] != replacement_id]
    parts[pidx] = part
    items[idx] = {**item, "parts": parts}
    _save(user_dir, items)
    return JSONResponse({"ok": True})
