"""Routes des bilans hebdomadaires."""
from __future__ import annotations

from typing import Any, Literal

from fastapi import APIRouter, Depends, Path, Query, Request
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.orm import Session

from ..core import audit, isoweek
from ..core.db import get_db
from ..models import Comment, User
from ..services import bilans as svc
from ..services.auth import Principal
from .deps import ApiError, client_ip, require

router = APIRouter(prefix="/api/bilans", tags=["bilans"])

Year = Path(ge=2000, le=2100)
Week = Path(ge=1, le=53)


def _week(y: int, w: int) -> None:
    try:
        isoweek.validate_week(y, w)
    except ValueError as e:
        raise ApiError(422, str(e), "bad_week") from None


def _wrap(fn: Any, *args: Any, **kwargs: Any) -> Any:
    try:
        return fn(*args, **kwargs)
    except svc.BilanError as e:
        raise ApiError(e.status, e.message, e.code, e.extra) from None


class RowIn(BaseModel):
    indicator_id: int
    value: int | str | None = None
    observation: str | None = Field(default="", max_length=4000)
    details: list[Any] | None = None


class DraftIn(BaseModel):
    revision: int = Field(ge=0)
    rows: list[RowIn] = Field(max_length=200)
    source: Literal["manuel", "collage"] = "manuel"


class ValidateIn(BaseModel):
    revision: int
    confirm: bool
    comment: str | None = Field(default=None, max_length=500)


class ReasonIn(BaseModel):
    reason: str = Field(max_length=500)


class StatusIn(BaseModel):
    status: Literal["brouillon", "a_verifier", "valide", "archive"]


class CopyIn(BaseModel):
    revision: int
    with_observations: bool = True


class CommentIn(BaseModel):
    body: str = Field(min_length=1, max_length=2000)


@router.get("")
def list_bilans(year: int | None = Query(default=None, ge=2000, le=2100), db: Session = Depends(get_db),
                p: Principal = Depends(require("bilan.read"))) -> dict[str, Any]:
    return {"bilans": svc.list_bilans(db, year)}


@router.get("/default")
def default(db: Session = Depends(get_db), p: Principal = Depends(require("bilan.read"))) -> dict[str, Any]:
    d = svc.default_week(db)
    cy, cw = isoweek.current_week()
    ey, ew = isoweek.shift_week(cy, cw, -1)
    return {"iso_year": d.iso_year, "iso_week": d.iso_week, "reason": d.reason, "drafts": d.drafts,
            "current": {"iso_year": cy, "iso_week": cw}, "last_completed": {"iso_year": ey, "iso_week": ew}}


@router.get("/{y}/{w}")
def get_bilan(y: int = Year, w: int = Week, db: Session = Depends(get_db),
              p: Principal = Depends(require("bilan.read"))) -> dict[str, Any]:
    _week(y, w)
    return svc.bilan_payload(db, y, w)


@router.put("/{y}/{w}/draft")
def save_draft(body: DraftIn, request: Request, y: int = Year, w: int = Week, db: Session = Depends(get_db),
               p: Principal = Depends(require("bilan.edit"))) -> dict[str, Any]:
    _week(y, w)
    rows = [r.model_dump() for r in body.rows]
    return _wrap(svc.save_draft, db, y, w, body.revision, rows, p.user, source=body.source, ip=client_ip(request))


@router.get("/{y}/{w}/check")
def check(y: int = Year, w: int = Week, db: Session = Depends(get_db),
          p: Principal = Depends(require("bilan.read"))) -> dict[str, Any]:
    _week(y, w)
    return svc.check(db, y, w)


@router.post("/{y}/{w}/validate")
def validate(body: ValidateIn, request: Request, y: int = Year, w: int = Week, db: Session = Depends(get_db),
             p: Principal = Depends(require("bilan.validate"))) -> dict[str, Any]:
    _week(y, w)
    if body.confirm is not True:
        raise ApiError(422, "La validation définitive doit être confirmée explicitement.", "confirmation_required")
    return _wrap(svc.validate, db, y, w, body.revision, p.user, body.comment, client_ip(request))


@router.post("/{y}/{w}/reopen")
def reopen(body: ReasonIn, request: Request, y: int = Year, w: int = Week, db: Session = Depends(get_db),
           p: Principal = Depends(require("bilan.reopen"))) -> dict[str, Any]:
    _week(y, w)
    return _wrap(svc.reopen, db, y, w, body.reason, p.user, client_ip(request))


@router.post("/{y}/{w}/status")
def set_status(body: StatusIn, request: Request, y: int = Year, w: int = Week, db: Session = Depends(get_db),
               p: Principal = Depends(require("bilan.edit"))) -> dict[str, Any]:
    _week(y, w)
    if body.status in {"archive", "valide"} and not p.can("bilan.archive"):
        raise ApiError(403, "Vous n'avez pas les droits nécessaires pour cette action.", "forbidden")
    return _wrap(svc.set_status, db, y, w, body.status, p.user, client_ip(request))


@router.post("/{y}/{w}/copy-previous")
def copy_previous(body: CopyIn, request: Request, y: int = Year, w: int = Week, db: Session = Depends(get_db),
                  p: Principal = Depends(require("bilan.edit"))) -> dict[str, Any]:
    _week(y, w)
    return _wrap(svc.copy_previous, db, y, w, body.revision, body.with_observations, p.user, client_ip(request))


@router.delete("/{y}/{w}/draft")
def clear_draft(request: Request, y: int = Year, w: int = Week, db: Session = Depends(get_db),
                p: Principal = Depends(require("bilan.edit"))) -> dict[str, Any]:
    _week(y, w)
    return _wrap(svc.clear_draft, db, y, w, p.user, client_ip(request))


@router.get("/{y}/{w}/versions")
def versions(y: int = Year, w: int = Week, db: Session = Depends(get_db),
             p: Principal = Depends(require("history.read"))) -> dict[str, Any]:
    _week(y, w)
    return {"versions": svc.list_versions(db, y, w)}


@router.post("/{y}/{w}/versions/{n}/restore")
def restore(request: Request, n: int = Path(ge=1), y: int = Year, w: int = Week, db: Session = Depends(get_db),
            p: Principal = Depends(require("bilan.edit", "bilan.reopen"))) -> dict[str, Any]:
    _week(y, w)
    return _wrap(svc.restore_version, db, y, w, n, p.user, client_ip(request))


@router.get("/{y}/{w}/history")
def history(y: int = Year, w: int = Week, db: Session = Depends(get_db),
            p: Principal = Depends(require("history.read"))) -> dict[str, Any]:
    _week(y, w)
    bilan = svc.find_bilan(db, y, w)
    comments = []
    if bilan is not None:
        rows = db.execute(select(Comment, User).join(User, User.id == Comment.user_id)
                          .where(Comment.bilan_id == bilan.id).order_by(Comment.id.desc())).all()
        comments = [{"id": c.id, "body": c.body, "user": u.display_name, "at": c.created_at.isoformat()}
                    for c, u in rows]
    return {"changes": svc.history(db, y, w), "comments": comments}


@router.post("/{y}/{w}/comments")
def add_comment(body: CommentIn, request: Request, y: int = Year, w: int = Week, db: Session = Depends(get_db),
                p: Principal = Depends(require("bilan.edit"))) -> dict[str, Any]:
    _week(y, w)
    bilan = svc.find_bilan(db, y, w)
    if bilan is None:
        raise ApiError(404, "Enregistrez d'abord le bilan avant de le commenter.", "not_found")
    c = Comment(bilan_id=bilan.id, user_id=p.user.id, body=svc.clean_text(body.body, 2000))
    db.add(c)
    audit.record(db, "bilan.commented", user_id=p.user.id, username=p.user.username, target_type="bilan",
                 target_id=f"{y}-S{w:02d}", ip=client_ip(request))
    db.commit()
    return {"ok": True}
