"""Requêtes d'agrégation pour les onglets "Suivi mensuel" de Gestion Comptable et
Gestion Commerciale (regroupement par mois, par fournisseur/client, par employé, par type),
avec filtres optionnels (mois, fournisseur, client, nature/type)."""

from sqlalchemy import case, desc, func
from sqlalchemy.orm import Session

from app import models

NOMS_MOIS = [
    "Janvier", "Février", "Mars", "Avril", "Mai", "Juin",
    "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre",
]


def formater_mois(cle: str | None) -> str:
    """Convertit "2026-08" en "Août 2026"."""
    if not cle or "-" not in cle:
        return cle or "—"
    annee, mois = cle.split("-")
    try:
        return f"{NOMS_MOIS[int(mois) - 1]} {annee}"
    except (ValueError, IndexError):
        return cle


GROUPEMENTS_PERIODE = ("mois", "trimestre", "annee")


def cle_periode(d, groupement: str) -> str:
    """Clé de regroupement chronologique (triable telle quelle) pour une date, selon la
    granularité choisie : "2026-08" (mois), "2026-T3" (trimestre) ou "2026" (année)."""
    if groupement == "annee":
        return str(d.year)
    if groupement == "trimestre":
        return f"{d.year}-T{(d.month - 1) // 3 + 1}"
    return f"{d.year}-{d.month:02d}"


def formater_periode(cle: str, groupement: str) -> str:
    """Libellé lisible pour une clé produite par cle_periode()."""
    if groupement == "annee":
        return cle
    if groupement == "trimestre":
        annee, trimestre = cle.split("-T")
        return f"T{trimestre} {annee}"
    return formater_mois(cle)


# ---------------------------------------------------------------------------
# Gestion Comptable
# ---------------------------------------------------------------------------
def lister_mois_comptable(db: Session) -> list[tuple[str, str]]:
    cle_achat = func.strftime("%Y-%m", models.Achat.date_achat)
    cle_frais = func.strftime("%Y-%m", models.AutreFrais.date_frais)
    mois_achats = {m for (m,) in db.query(cle_achat).distinct().all() if m}
    mois_frais = {m for (m,) in db.query(cle_frais).distinct().all() if m}
    mois = sorted(mois_achats | mois_frais, reverse=True)
    return [(m, formater_mois(m)) for m in mois]


def lister_types_frais(db: Session) -> list[str]:
    return sorted({t for (t,) in db.query(models.AutreFrais.type_frais).distinct().all() if t})


def lister_employes(db: Session) -> list[str]:
    return sorted({e for (e,) in db.query(models.FraisPersonnel.employe).distinct().all() if e})


def resume_periode_comptable(
    db: Session, groupement: str = "mois", fournisseur_id: int | None = None, type_frais: str | None = None
) -> list[dict]:
    """Résumé achats + autres frais + frais de personnel regroupé par mois, trimestre ou
    année (paramètre `groupement`), avec les mêmes filtres fournisseur/nature que les
    autres tableaux. Les frais de personnel sans date renseignée (anciennes saisies non
    encore mises à jour) n'apparaissent pas dans ce résumé chronologique."""
    if groupement not in GROUPEMENTS_PERIODE:
        groupement = "mois"

    def _regrouper(paires) -> dict[str, float]:
        resultat: dict[str, float] = {}
        for d, montant in paires:
            if d is None:
                continue
            cle = cle_periode(d, groupement)
            resultat[cle] = resultat.get(cle, 0) + montant
        return resultat

    q_achats = db.query(models.Achat.date_achat, models.Achat.montant_total)
    if fournisseur_id:
        q_achats = q_achats.filter(models.Achat.fournisseur_id == fournisseur_id)
    achats_periode = _regrouper(q_achats.all())

    q_frais = db.query(models.AutreFrais.date_frais, models.AutreFrais.montant)
    if type_frais:
        q_frais = q_frais.filter(models.AutreFrais.type_frais == type_frais)
    frais_periode = _regrouper(q_frais.all())

    q_personnel = db.query(models.FraisPersonnel.date_debut, models.FraisPersonnel.montant)
    personnel_periode = _regrouper(q_personnel.all())

    cles = sorted(set(achats_periode) | set(frais_periode) | set(personnel_periode), reverse=True)
    resultat = []
    for cle in cles:
        achats = achats_periode.get(cle) or 0
        frais = frais_periode.get(cle) or 0
        personnel = personnel_periode.get(cle) or 0
        resultat.append(
            {
                "periode": formater_periode(cle, groupement),
                "achats": achats,
                "autres_frais": frais,
                "frais_personnel": personnel,
                "total": achats + frais + personnel,
            }
        )
    return resultat


def achats_par_fournisseur(db: Session, mois: str | None = None) -> list[tuple]:
    q = db.query(models.Fournisseur.nom, func.count(models.Achat.id), func.sum(models.Achat.montant_total)).join(
        models.Achat, models.Achat.fournisseur_id == models.Fournisseur.id
    )
    if mois:
        q = q.filter(func.strftime("%Y-%m", models.Achat.date_achat) == mois)
    return q.group_by(models.Fournisseur.id).order_by(desc(func.sum(models.Achat.montant_total))).all()


def frais_personnel_par_employe(db: Session, periode: str | None = None) -> list[tuple]:
    q = db.query(models.FraisPersonnel.employe, func.sum(models.FraisPersonnel.montant))
    if periode:
        q = q.filter(models.FraisPersonnel.periode == periode)
    return q.group_by(models.FraisPersonnel.employe).order_by(desc(func.sum(models.FraisPersonnel.montant))).all()


def frais_personnel_par_periode(db: Session, employe: str | None = None) -> list[tuple]:
    q = db.query(models.FraisPersonnel.periode, func.sum(models.FraisPersonnel.montant))
    if employe:
        q = q.filter(models.FraisPersonnel.employe == employe)
    return q.group_by(models.FraisPersonnel.periode).order_by(desc(func.max(models.FraisPersonnel.id))).all()


def autres_frais_par_type(db: Session, mois: str | None = None) -> list[tuple]:
    q = db.query(models.AutreFrais.type_frais, func.sum(models.AutreFrais.montant))
    if mois:
        q = q.filter(func.strftime("%Y-%m", models.AutreFrais.date_frais) == mois)
    return q.group_by(models.AutreFrais.type_frais).order_by(desc(func.sum(models.AutreFrais.montant))).all()


THEMES_RAPPORT = {
    "personnel": "Frais de personnel seul",
    "autres": "Autres frais seul",
    "personnel_autres": "Frais de personnel + Autres frais",
    "fournisseurs": "Achats fournisseurs",
    "tout": "Ensemble (achats + autres frais + frais de personnel)",
}

# Sous-ensemble de thèmes proposés depuis l'onglet Ferme Exploitation (pas d'achats fournisseurs).
THEMES_RAPPORT_FERME = {
    "personnel": "Frais de personnel",
    "autres": "Autres frais",
    "personnel_autres": "Ensemble (frais de personnel + autres frais)",
}


def achats_periode(db: Session, date_debut, date_fin) -> list:
    return (
        db.query(models.Achat)
        .filter(models.Achat.date_achat >= date_debut, models.Achat.date_achat <= date_fin)
        .order_by(models.Achat.date_achat)
        .all()
    )


def achats_par_fournisseur_periode(db: Session, date_debut, date_fin) -> list[tuple]:
    q = (
        db.query(models.Fournisseur.nom, func.count(models.Achat.id), func.sum(models.Achat.montant_total))
        .join(models.Achat, models.Achat.fournisseur_id == models.Fournisseur.id)
        .filter(models.Achat.date_achat >= date_debut, models.Achat.date_achat <= date_fin)
    )
    return q.group_by(models.Fournisseur.id).order_by(desc(func.sum(models.Achat.montant_total))).all()


def autres_frais_periode(db: Session, date_debut, date_fin) -> list:
    return (
        db.query(models.AutreFrais)
        .filter(models.AutreFrais.date_frais >= date_debut, models.AutreFrais.date_frais <= date_fin)
        .order_by(models.AutreFrais.date_frais)
        .all()
    )


def autres_frais_par_type_periode(db: Session, date_debut, date_fin) -> list[tuple]:
    q = db.query(models.AutreFrais.type_frais, func.sum(models.AutreFrais.montant)).filter(
        models.AutreFrais.date_frais >= date_debut, models.AutreFrais.date_frais <= date_fin
    )
    return q.group_by(models.AutreFrais.type_frais).order_by(desc(func.sum(models.AutreFrais.montant))).all()


def frais_personnel_periode(db: Session, date_debut, date_fin) -> list:
    return (
        db.query(models.FraisPersonnel)
        .filter(models.FraisPersonnel.date_debut >= date_debut, models.FraisPersonnel.date_debut <= date_fin)
        .order_by(models.FraisPersonnel.date_debut)
        .all()
    )


def frais_personnel_par_employe_periode(db: Session, date_debut, date_fin) -> list[tuple]:
    q = db.query(models.FraisPersonnel.employe, func.sum(models.FraisPersonnel.montant)).filter(
        models.FraisPersonnel.date_debut >= date_debut, models.FraisPersonnel.date_debut <= date_fin
    )
    return q.group_by(models.FraisPersonnel.employe).order_by(desc(func.sum(models.FraisPersonnel.montant))).all()


def grand_livre_fournisseur(
    db: Session,
    fournisseur: models.Fournisseur,
    date_debut,
    date_fin,
) -> dict:
    """Construit le grand livre d'un fournisseur sur une période : solde d'ouverture
    (solde initial + tout mouvement antérieur à date_debut) puis chaque achat (débit) et
    règlement (crédit) de la période, triés chronologiquement, avec solde courant après
    chaque ligne (colonne "Solde DB" si le solde est dû au fournisseur, "Solde CR" sinon)."""
    achats_avant = [a for a in fournisseur.achats if a.date_achat < date_debut]
    reglements_avant = [r for r in fournisseur.reglements if r.date_reglement < date_debut]
    solde_ouverture = (
        fournisseur.solde_initial
        + sum(a.montant_total for a in achats_avant)
        - sum(r.montant for r in reglements_avant)
    )

    mouvements = []
    for a in fournisseur.achats:
        if date_debut <= a.date_achat <= date_fin:
            libelle = a.designation_produit
            if a.numero_facture:
                libelle = f"{a.numero_facture} — {libelle}"
            mouvements.append(
                {"date": a.date_achat, "id": a.id, "journal": "Achat", "reference": a.numero_facture or "",
                 "libelle": libelle, "debit": a.montant_total, "credit": 0.0}
            )
    for r in fournisseur.reglements:
        if date_debut <= r.date_reglement <= date_fin:
            libelle = f"Règlement {r.mode_paiement}"
            if r.reference:
                libelle += f" n°{r.reference}"
            if r.achat and r.achat.numero_facture:
                libelle += f" (facture {r.achat.numero_facture})"
            mouvements.append(
                {"date": r.date_reglement, "id": r.id, "journal": r.mode_paiement, "reference": r.reference or "",
                 "libelle": libelle, "debit": 0.0, "credit": r.montant}
            )
    mouvements.sort(key=lambda m: (m["date"], m["journal"] != "Achat", m["id"]))

    solde = solde_ouverture
    lignes = []
    for m in mouvements:
        solde += m["debit"] - m["credit"]
        lignes.append({**m, "solde_db": solde if solde >= 0 else 0.0, "solde_cr": -solde if solde < 0 else 0.0})

    total_debit = sum(m["debit"] for m in mouvements)
    total_credit = sum(m["credit"] for m in mouvements)
    return {
        "fournisseur": fournisseur,
        "date_debut": date_debut,
        "date_fin": date_fin,
        "solde_ouverture": solde_ouverture,
        "lignes": lignes,
        "total_debit": total_debit,
        "total_credit": total_credit,
        "solde_final": solde,
    }


# ---------------------------------------------------------------------------
# Gestion Commerciale
# ---------------------------------------------------------------------------
def lister_mois_commercial(db: Session) -> list[tuple[str, str]]:
    cle = func.strftime("%Y-%m", models.VenteClient.date_vente)
    mois = sorted(
        {
            m
            for (m,) in db.query(cle).filter(models.VenteClient.type_document != "devis").distinct().all()
            if m
        },
        reverse=True,
    )
    return [(m, formater_mois(m)) for m in mois]


def resume_mensuel_commercial(db: Session, client_id: int | None = None) -> list[dict]:
    cle = func.strftime("%Y-%m", models.VenteClient.date_vente)
    montant_signe = case((models.VenteClient.type_document == "avoir", -models.VenteClient.montant), else_=models.VenteClient.montant)
    q = db.query(cle, func.count(models.VenteClient.id), func.sum(montant_signe)).filter(
        models.VenteClient.type_document != "devis"
    )
    if client_id:
        q = q.filter(models.VenteClient.client_id == client_id)
    lignes = q.group_by(cle).order_by(desc(cle)).all()
    return [{"mois": formater_mois(m), "nb_ventes": n, "total": t or 0} for m, n, t in lignes if m]


def ventes_par_client(db: Session, mois: str | None = None) -> list[tuple]:
    montant_signe = case((models.VenteClient.type_document == "avoir", -models.VenteClient.montant), else_=models.VenteClient.montant)
    q = db.query(models.Client.nom, func.count(models.VenteClient.id), func.sum(montant_signe)).join(
        models.VenteClient, models.VenteClient.client_id == models.Client.id
    ).filter(models.VenteClient.type_document != "devis")
    if mois:
        q = q.filter(func.strftime("%Y-%m", models.VenteClient.date_vente) == mois)
    return q.group_by(models.Client.id).order_by(desc(func.sum(montant_signe))).all()


def grand_livre_client(
    db: Session,
    client: models.Client,
    date_debut,
    date_fin,
) -> dict:
    """Construit le grand livre d'un client sur une période : solde d'ouverture (solde
    initial + tout mouvement antérieur à date_debut) puis chaque facture (débit), avoir
    (crédit) et encaissement (crédit) de la période, triés chronologiquement, avec solde
    courant après chaque ligne (colonne "Solde DB" si le client est débiteur, "Solde CR"
    sinon). Les devis n'apparaissent jamais dans le grand livre : ce ne sont pas des
    engagements financiers."""
    factures = [v for v in client.ventes if v.type_document == "facture"]
    avoirs = [v for v in client.ventes if v.type_document == "avoir"]

    factures_avant = [v for v in factures if v.date_vente < date_debut]
    avoirs_avant = [v for v in avoirs if v.date_vente < date_debut]
    encaissements_avant = [e for e in client.encaissements if e.date_encaissement < date_debut]
    solde_ouverture = (
        client.solde_initial
        + sum(v.montant for v in factures_avant)
        - sum(v.montant for v in avoirs_avant)
        - sum(e.montant for e in encaissements_avant)
    )

    mouvements = []
    for v in factures:
        if date_debut <= v.date_vente <= date_fin:
            libelle = v.produit
            if v.numero_facture:
                libelle = f"{v.numero_facture} — {libelle}"
            mouvements.append(
                {"date": v.date_vente, "id": v.id, "journal": "Facture", "reference": v.numero_facture or "",
                 "libelle": libelle, "debit": v.montant, "credit": 0.0}
            )
    for v in avoirs:
        if date_debut <= v.date_vente <= date_fin:
            libelle = v.produit
            if v.numero_facture:
                libelle = f"{v.numero_facture} — {libelle}"
            if v.facture_origine and v.facture_origine.numero_facture:
                libelle += f" (avoir sur {v.facture_origine.numero_facture})"
            mouvements.append(
                {"date": v.date_vente, "id": v.id, "journal": "Avoir", "reference": v.numero_facture or "",
                 "libelle": libelle, "debit": 0.0, "credit": v.montant}
            )
    for e in client.encaissements:
        if date_debut <= e.date_encaissement <= date_fin:
            libelle = f"Encaissement {e.mode_paiement}"
            if e.reference:
                libelle += f" n°{e.reference}"
            if e.vente and e.vente.numero_facture:
                libelle += f" (facture {e.vente.numero_facture})"
            mouvements.append(
                {"date": e.date_encaissement, "id": e.id, "journal": e.mode_paiement, "reference": e.reference or "",
                 "libelle": libelle, "debit": 0.0, "credit": e.montant}
            )
    mouvements.sort(key=lambda m: (m["date"], m["journal"] != "Facture", m["id"]))

    solde = solde_ouverture
    lignes = []
    for m in mouvements:
        solde += m["debit"] - m["credit"]
        lignes.append({**m, "solde_db": solde if solde >= 0 else 0.0, "solde_cr": -solde if solde < 0 else 0.0})

    total_debit = sum(m["debit"] for m in mouvements)
    total_credit = sum(m["credit"] for m in mouvements)
    return {
        "client": client,
        "date_debut": date_debut,
        "date_fin": date_fin,
        "solde_ouverture": solde_ouverture,
        "lignes": lignes,
        "total_debit": total_debit,
        "total_credit": total_credit,
        "solde_final": solde,
    }


PREFIXES_NUMERO = {"devis": "D", "facture": "F", "avoir": "AV"}


def suggerer_numero_document(db: Session, type_document: str) -> str:
    """Suggère le prochain numéro pour un type de document (ex. F-2026-0007), à partir
    du plus grand numéro existant cette année pour ce type. Reste modifiable par
    l'utilisateur — ce n'est qu'une aide, pas une contrainte d'unicité en base."""
    from datetime import date as _date

    prefixe = PREFIXES_NUMERO.get(type_document, "F")
    annee = _date.today().year
    motif = f"{prefixe}-{annee}-"
    existants = (
        db.query(models.VenteClient.numero_facture)
        .filter(models.VenteClient.numero_facture.like(f"{motif}%"))
        .all()
    )
    max_seq = 0
    for (numero,) in existants:
        suffixe = numero[len(motif):]
        if suffixe.isdigit():
            max_seq = max(max_seq, int(suffixe))
    return f"{motif}{max_seq + 1:04d}"
