"""Budget transparency endpoints (/api/budget)."""
from __future__ import annotations

import json
import uuid
from datetime import date
from pathlib import Path

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

from bincio.serve import deps
from bincio.serve.db import User

router = APIRouter()

_BUDGET_FILE = "budget.json"


def _budget_path() -> Path:
    return deps._get_data_dir() / _BUDGET_FILE


def _load() -> dict:
    p = _budget_path()
    if not p.exists():
        return {"monthly_target_eur": None, "entries": []}
    try:
        return json.loads(p.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return {"monthly_target_eur": None, "entries": []}


def _save(data: dict) -> None:
    _budget_path().write_text(
        json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8"
    )


def _materialise_recurring(data: dict) -> bool:
    """Auto-add a copy of each recurring entry for the current month if absent.

    Returns True if data was modified (caller should save).
    Copies reference the template via recurring_from so they're not re-generated.
    """
    current = date.today().strftime("%Y-%m")
    templates = [e for e in data.get("entries", []) if e.get("recurring")]
    if not templates:
        return False
    modified = False
    for t in templates:
        if t["month"] == current:
            continue  # template is already this month
        already = any(
            e.get("recurring_from") == t["id"] and e["month"] == current
            for e in data["entries"]
        )
        if not already:
            data["entries"].append({
                "id": str(uuid.uuid4())[:8],
                "type": t["type"],
                "label": t["label"],
                "amount_eur": t["amount_eur"],
                "month": current,
                "note": t.get("note", ""),
                "recurring_from": t["id"],
            })
            modified = True
    return modified


@router.get("/api/budget")
async def get_budget() -> JSONResponse:
    data = _load()
    if _materialise_recurring(data):
        _save(data)
    return JSONResponse(data)


@router.post("/api/budget/settings")
async def update_settings(
    request: Request,
    _: User = Depends(deps._require_admin),
) -> JSONResponse:
    body = await request.json()
    data = _load()
    if "monthly_target_eur" in body:
        v = body["monthly_target_eur"]
        data["monthly_target_eur"] = round(float(v), 2) if v is not None else None
    _save(data)
    return JSONResponse({"ok": True})


@router.post("/api/budget/entries")
async def add_entry(
    request: Request,
    _: User = Depends(deps._require_admin),
) -> JSONResponse:
    body = await request.json()
    entry_type = body.get("type")
    if entry_type not in ("donation", "expense"):
        raise HTTPException(400, "type must be 'donation' or 'expense'")
    label = str(body.get("label", "")).strip()
    if not label:
        raise HTTPException(400, "label is required")
    try:
        amount = round(float(body["amount_eur"]), 2)
    except (KeyError, TypeError, ValueError):
        raise HTTPException(400, "amount_eur must be a number")
    month = str(body.get("month", "")).strip()
    if len(month) != 7 or month[4] != "-":
        raise HTTPException(400, "month must be YYYY-MM")
    note = str(body.get("note", "")).strip()

    entry: dict = {
        "id": str(uuid.uuid4())[:8],
        "type": entry_type,
        "label": label,
        "amount_eur": amount,
        "month": month,
        "note": note,
    }
    if body.get("recurring"):
        entry["recurring"] = True
    data = _load()
    data.setdefault("entries", []).append(entry)
    _save(data)
    return JSONResponse(entry, status_code=201)


@router.patch("/api/budget/entries/{entry_id}")
async def update_entry(
    entry_id: str,
    request: Request,
    _: User = Depends(deps._require_admin),
) -> JSONResponse:
    body = await request.json()
    data = _load()
    entry = next((e for e in data.get("entries", []) if e["id"] == entry_id), None)
    if not entry:
        raise HTTPException(404, "Entry not found")
    if "label" in body:
        entry["label"] = str(body["label"]).strip()
    if "type" in body:
        if body["type"] not in ("donation", "expense"):
            raise HTTPException(400, "type must be 'donation' or 'expense'")
        entry["type"] = body["type"]
    if "amount_eur" in body:
        entry["amount_eur"] = round(float(body["amount_eur"]), 2)
    if "month" in body:
        entry["month"] = str(body["month"]).strip()
    if "note" in body:
        entry["note"] = str(body["note"]).strip()
    if "recurring" in body:
        if body["recurring"]:
            entry["recurring"] = True
        else:
            entry.pop("recurring", None)
    _save(data)
    return JSONResponse(entry)


@router.delete("/api/budget/entries/{entry_id}")
async def delete_entry(
    entry_id: str,
    _: User = Depends(deps._require_admin),
) -> JSONResponse:
    data = _load()
    before = len(data.get("entries", []))
    data["entries"] = [e for e in data.get("entries", []) if e["id"] != entry_id]
    if len(data["entries"]) == before:
        raise HTTPException(404, "Entry not found")
    _save(data)
    return JSONResponse({"ok": True})
