"""
Source de données Joseph — remplace `clickup_client.py`.

Joseph pousse un JSON produit par `App\\Services\\Reporting\\ReportPayload` ;
cette couche le transforme en `Task`, l'objet que les générateurs consomment.
**Aucun générateur n'est modifié** : c'est tout l'intérêt d'avoir un point de
raccordement aussi étroit.

Deux différences de fond avec la source ClickUp, toutes deux en notre faveur :

- **Le type est un code, pas un entier magique.** ClickUp identifiait ses types
  par un `custom_item_id` (0, 1, 1003, 1004) ; Joseph envoie `task`, `milestone`,
  `chantier`, `meeting`. La conversion vers les constantes internes se fait ici,
  d'un seul côté.
- **Le statut arrive avec sa catégorie.** ClickUp ne donnait qu'un libellé libre,
  que `data_model` devait reconnaître par une liste de synonymes — « Bloqué »
  dépendait donc de l'orthographe. Joseph envoie `blocked`, et le doute
  disparaît.
"""

from __future__ import annotations  # PEP 585 (`list[Task]`) sans Python 3.9 : l'hébergement est en 3.7


import json
import sys
from datetime import datetime
from typing import Optional

from core.data_model import (
    TYPE_CHANTIER,
    TYPE_JALON,
    TYPE_REUNION,
    TYPE_TACHE,
    TYPE_LABELS,
    Task,
)

# La version du contrat que ce fichier sait lire. `ReportPayload` l'émet, et un
# écart doit s'arrêter ici plutôt que produire un document silencieusement faux.
SUPPORTED_VERSION = 1

# Les quatre types de Joseph sont exactement ceux que les générateurs
# connaissent — c'est ce qui rend la reprise possible sans les toucher.
TYPE_BY_CODE = {
    "task": TYPE_TACHE,
    "milestone": TYPE_JALON,
    "chantier": TYPE_CHANTIER,
    "meeting": TYPE_REUNION,
}


def parse_date(value) -> Optional[datetime]:
    """
    Accepte une date seule (`2026-03-09`) ou un horodatage ISO complet.

    Publique et non privée : `render.py` s'en sert aussi pour lire les bornes de
    période d'un bloc — c'est le même format, il n'y a aucune raison d'en écrire
    une seconde.
    """
    if not value:
        return None
    try:
        # `fromisoformat` de Python 3.7 ne digère ni le `Z` ni les fuseaux
        # notés `+02:00` sans séparateur — on normalise avant de lui passer la
        # chaîne. L'hébergement est en 3.7 : pas de `datetime.fromisoformat`
        # tolérant avant 3.11.
        text = str(value).replace("Z", "+00:00")
        if len(text) == 10:
            return datetime.strptime(text, "%Y-%m-%d")
        return datetime.fromisoformat(text).replace(tzinfo=None)
    except (ValueError, TypeError):
        return None


def from_joseph(raw: dict) -> Task:
    """Construit une `Task` depuis une entrée `tasks[]` du payload Joseph."""
    item_type = TYPE_BY_CODE.get(raw.get("type"), TYPE_TACHE)

    return Task(
        id=str(raw.get("id", "")),
        name=raw.get("name") or "",
        item_type=item_type,
        item_type_label=TYPE_LABELS.get(item_type, "Tâche"),

        start_date=parse_date(raw.get("start_date")),
        due_date=parse_date(raw.get("due_date")),
        date_done=parse_date(raw.get("date_done")),

        status=(raw.get("status") or "").lower().strip(),
        # Joseph a déjà tranché : on ne redevine rien.
        is_done=bool(raw.get("is_done")),
        is_at_risk=bool(raw.get("is_at_risk")),
        is_waiting=bool(raw.get("is_waiting")),
        is_cancelled=bool(raw.get("is_cancelled")),
        status_category=raw.get("status_category"),

        priority=raw.get("priority"),
        priority_label=raw.get("priority") or "",

        description=raw.get("description"),

        # Les deux cases de reporting de ClickUp n'en font qu'une côté Joseph :
        # `reporting_visible`. On alimente les deux champs avec, pour que les
        # générateurs — qui les lisent séparément — continuent de fonctionner.
        visible_copil=bool(raw.get("reporting_visible")),
        visible_rapport=bool(raw.get("reporting_visible")),
        a_arbitrer=False,
        note_contexte=raw.get("note_contexte"),
        description_gantt=raw.get("description_gantt"),

        folder_name=raw.get("folder_name"),
        list_name=raw.get("list_name") or "",
        list_id=str(raw.get("list_id") or ""),
        space_name=raw.get("space_name"),

        url=raw.get("url") or "",
        parent_id=raw.get("parent_id"),
        orderindex=float(raw.get("orderindex") or 0),

        # Résolue côté Joseph — héritage du chantier racine compris. Le
        # générateur n'a donc pas à connaître l'arbre.
        color=raw.get("color"),
        color_light=raw.get("color_light"),

        code=raw.get("code"),
        assignees=list(raw.get("assignees") or []),
        is_major=bool(raw.get("is_major_milestone")),
        effort_hours=raw.get("effort_hours"),
    )


def parse_payload(payload: dict) -> list:
    """
    Transforme le payload complet en liste de `Task`.

    Les éléments annulés et ceux masqués du reporting sont écartés **ici** et non
    dans les générateurs : c'est une décision sur les données, pas sur la mise en
    page, et la faire une fois évite que les trois formats divergent.
    """
    version = payload.get("version")
    if version != SUPPORTED_VERSION:
        raise ValueError(
            f"Version de contrat non supportée : {version!r} "
            f"(ce générateur lit la version {SUPPORTED_VERSION})"
        )

    tasks = []
    for raw in payload.get("tasks", []):
        task = from_joseph(raw)
        if task.is_cancelled:
            continue
        if not raw.get("reporting_visible", True):
            continue
        tasks.append(task)

    return tasks


def load(stream=None) -> dict:
    """
    Lit le payload sur l'entrée standard.

    Par l'entrée standard et non par un fichier temporaire : Joseph tourne sur un
    mutualisé où `/tmp` est partagé, et le contenu d'un rapport n'a pas à
    transiter par un fichier lisible le temps de la génération.
    """
    return json.load(stream or sys.stdin)
