"""Agrégation en lecture des documents des modules métier.

Ce service ne possède aucun fichier et ne crée aucune ligne documentaire : il
ne fait que présenter des adaptateurs homogènes aux vues du centre global.
"""
from dataclasses import dataclass
from datetime import date, datetime
from typing import Optional

from flask import url_for

from ..core.authorization import has_permission
from ..core.models.college import Building, Room, Zone
from ..core.models.cleaning import ProductDocument
from ..core.models.equipment import EquipmentDocument
from ..core.models.maintenance import InterventionDocument


DOCUMENT_TYPE_LABELS = {
    "notice": "Notice / manuel",
    "facture": "Facture",
    "devis": "Devis",
    "rapport": "Rapport / compte rendu",
    "certificat": "Certificat / contrôle",
    "photo": "Photo",
    "plan": "Plan",
    "fds": "FDS",
    "fiche_technique": "Fiche technique",
    "autre": "Autre",
    "other": "Autre",
}


@dataclass
class DocumentResult:
    source: str
    source_id: int
    parent_id: int
    title: str
    filename: str
    document_type: str
    document_type_label: str
    description: Optional[str]
    document_date: Optional[date]
    context_label: str
    context_url: Optional[str]
    download_url: str
    filepath: str
    location_labels: tuple[str, ...]
    location_summary: str
    version: Optional[str] = None
    issuer: Optional[str] = None


def _location_path(room):
    if not room:
        return None
    parts = []
    if room.building:
        parts.append(room.building.name)
    if room.zone:
        parts.append(room.zone.name)
    parts.append(room.name)
    return " > ".join(parts)


def _equipment_location(equipment):
    if not equipment:
        return ()
    room = equipment.effective_room or equipment.room
    path = _location_path(room)
    return (path,) if path else ()


def _intervention_location(intervention):
    return _equipment_location(intervention.equipment) or (
        (_location_path(intervention.room),) if _location_path(intervention.room) else ()
    )


def _product_locations(product):
    paths = set()
    for lot in product.lots:
        for balance in lot.balances:
            if not balance.quantity or balance.quantity <= 0 or not balance.location:
                continue
            location = balance.location
            parts = []
            building = Building.query.get(location.building_id) if location.building_id else None
            zone = Zone.query.get(location.zone_id) if location.zone_id else None
            room = Room.query.get(location.room_id) if location.room_id else None
            if building:
                parts.append(building.name)
            if zone:
                parts.append(zone.name)
            if room:
                parts.append(room.name)
            parts.append(location.name)
            paths.add(" > ".join(parts))
    return tuple(sorted(paths))


def _summary(locations):
    if not locations:
        return "Localisation non renseignée"
    if len(locations) == 1:
        return locations[0]
    return f"{len(locations)} emplacements"


def _result(source, document, parent, *, title, document_type, description,
            document_date, context_label, context_url, locations, version=None,
            issuer=None):
    return DocumentResult(
        source=source,
        source_id=document.id,
        parent_id=parent.id,
        title=title or document.filename,
        filename=document.filename,
        document_type=document_type or "autre",
        document_type_label=DOCUMENT_TYPE_LABELS.get(document_type or "autre", "Autre"),
        description=description,
        document_date=document_date,
        context_label=context_label,
        context_url=context_url,
        download_url=url_for("documents.download", source=source, document_id=document.id),
        filepath=document.filepath,
        location_labels=tuple(locations),
        location_summary=_summary(locations),
        version=version,
        issuer=issuer,
    )


def list_documents(*, query="", source=None, document_type=None,
                   date_from=None, date_to=None, user=None, fds_only=False):
    """Retourne les documents accessibles, filtrés sans stockage parallèle."""
    user = user
    results = []
    if has_permission("documents.view", user) and has_permission("intervention.view", user):
        for document in InterventionDocument.query.order_by(InterventionDocument.uploaded_at.desc()).all():
            parent = document.intervention
            if not parent:
                continue
            item = _result(
                "intervention", document, parent, title=document.filename,
                document_type=getattr(document, "document_type", "autre"),
                description=document.description, document_date=document.uploaded_at,
                context_label=f"Intervention #{parent.id} — {parent.title}",
                context_url=url_for("interventions.detail", id=parent.id),
                locations=_intervention_location(parent),
            )
            results.append(item)
    if has_permission("documents.view", user) and has_permission("patrimoine.view", user):
        for document in EquipmentDocument.query.order_by(EquipmentDocument.uploaded_at.desc()).all():
            parent = document.equipment
            if not parent:
                continue
            results.append(_result(
                "equipment", document, parent, title=document.filename,
                document_type=getattr(document, "document_type", "autre"),
                description=document.description, document_date=document.uploaded_at,
                context_label=f"Équipement — {parent.name}",
                context_url=url_for("equipments.detail", id=parent.id),
                locations=_equipment_location(parent),
            ))
    if has_permission("documents.view", user) and has_permission("stock.view", user):
        for document in ProductDocument.query.order_by(ProductDocument.uploaded_at.desc()).all():
            parent = document.commercial_product
            if not parent:
                continue
            generic = parent.generic_product
            label = parent.commercial_name
            results.append(_result(
                "product", document, parent, title=document.title or document.filename,
                document_type=document.document_type, description=None,
                document_date=document.published_at or document.uploaded_at,
                context_label=f"Produit — {label}",
                context_url=url_for("cleaning.references", highlight=parent.id),
                locations=_product_locations(parent),
                version=document.version,
                issuer=(parent.manufacturer.name if parent.manufacturer else None)
                or (parent.supplier.name if parent.supplier else None),
            ))
    needle = (query or "").strip().casefold()
    if needle:
        results = [item for item in results if needle in " ".join((
            item.title, item.filename, item.description or "", item.context_label,
            item.location_summary,
        )).casefold()]
    if source:
        results = [item for item in results if item.source == source]
    if document_type:
        results = [item for item in results if item.document_type == document_type]
    if fds_only:
        results = [item for item in results if item.document_type == "fds"]
    def document_day(item):
        if not item.document_date:
            return None
        return item.document_date.date() if isinstance(item.document_date, datetime) else item.document_date
    if date_from:
        results = [item for item in results if document_day(item) and document_day(item) >= date_from]
    if date_to:
        results = [item for item in results if document_day(item) and document_day(item) <= date_to]
    # Les sources historiques mélangent ``date`` (produits) et ``datetime``
    # (interventions/équipements).  Normaliser avant le tri évite une
    # TypeError Python et garde un ordre stable entre les trois adaptateurs.
    results.sort(
        key=lambda item: datetime.combine(document_day(item), datetime.min.time())
        if document_day(item) else datetime.min,
        reverse=True,
    )
    return results


def get_document(source, document_id):
    models = {
        "intervention": InterventionDocument,
        "equipment": EquipmentDocument,
        "product": ProductDocument,
    }
    model = models.get(source)
    return model.query.get_or_404(document_id) if model else None


def can_read_source(source, user=None):
    return has_permission("documents.view", user) and has_permission({
        "intervention": "intervention.view",
        "equipment": "patrimoine.view",
        "product": "stock.view",
    }.get(source, "system.admin"), user)
