"""/api/plus-projects — the PlusROI-shaped Projects create/edit form
(phase-3 slice 2a). Thin HTTP layer over ``services/projects_plus.py``
(owner gate, sheet-owned write-block, tenant scope, client-default
inheritance, recurrence-vs-milestones validation, audit all live in the
service). Distinct from ``/api/projects`` (the Bowden Works reporting
rollup) — this is additive and does not touch that surface."""

from __future__ import annotations

import uuid

from fastapi import APIRouter
from pydantic import BaseModel

from app.routers.deps import ActorDep, ApiError, SessionDep
from app.services import projects_plus as svc
from app.services.errors import ServiceError

router = APIRouter()


def _run(fn, *args, **kwargs):
    try:
        return fn(*args, **kwargs)
    except ServiceError as exc:
        raise ApiError(exc.status, exc.code, exc.summary, exc.detail) from exc


class MilestoneInput(BaseModel):
    label: str = ""
    amount: str | None = None


class PlusProjectCreate(BaseModel):
    client_party_id: str = ""
    name: str = ""
    description: str | None = None
    # inherited-from-client defaults (editable)
    currency: str | None = None
    terms: str | None = None
    invoice_timing: str | None = None
    default_commission_pct: str | None = None
    commission_party_id: str | None = None
    # recurrence XOR milestones
    is_recurring: bool = False
    cycle_months: int | None = None
    billing_timing: str | None = None
    amount: str | None = None
    milestones: list[MilestoneInput] = []


class PlusProjectPatch(BaseModel):
    """PATCH semantics: only the SET fields reach the service (an omitted
    field is never touched)."""

    name: str | None = None
    description: str | None = None
    status: str | None = None
    currency: str | None = None
    terms: str | None = None
    invoice_timing: str | None = None
    default_commission_pct: str | None = None
    commission_party_id: str | None = None
    is_recurring: bool | None = None
    cycle_months: int | None = None
    billing_timing: str | None = None
    amount: str | None = None
    milestones: list[MilestoneInput] | None = None


@router.get("/api/plus-projects")
def list_projects(
    session: SessionDep,
    actor: ActorDep,
    recurring: bool | None = None,
    timing: str | None = None,
) -> dict:
    return _run(
        svc.list_projects, session, actor, recurring=recurring, timing=timing
    )


@router.post("/api/plus-projects")
def create_project(
    session: SessionDep, actor: ActorDep, body: PlusProjectCreate
) -> dict:
    return _run(svc.create_project, session, actor, body.model_dump())


@router.get("/api/plus-projects/{project_id}")
def get_project(
    session: SessionDep, actor: ActorDep, project_id: uuid.UUID
) -> dict:
    return _run(svc.get_project, session, actor, project_id)


@router.patch("/api/plus-projects/{project_id}")
def update_project(
    session: SessionDep,
    actor: ActorDep,
    project_id: uuid.UUID,
    body: PlusProjectPatch,
) -> dict:
    patch = {k: getattr(body, k) for k in body.model_fields_set}
    # pydantic gives milestones as MilestoneInput models — hand the service
    # plain dicts (it reads .get('label')/.get('amount')).
    if "milestones" in patch and patch["milestones"] is not None:
        patch["milestones"] = [m.model_dump() for m in patch["milestones"]]
    return _run(svc.update_project, session, actor, project_id, patch)
