import uuid

from fastapi import HTTPException
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.commentaire import Commentaire
from app.models.poste import Poste
from app.models.reaction import Reaction
from app.models.utilisateur import Utilisateur
from app.realtime import events
from app.services.stats_service import StatsService
from app.schemas.poste import CommentaireCreate, PosteCreate, ReactionCreate

VALID_POSTE_STATUTS = {
    "NOUVEAU",
    "APPROUVER_IA",
    "REJETER_IA",
    "APPROUVER",
    "REJETER",
    "ARCHIVER",
}
APPROVED_POSTE_STATUTS = {"APPROUVER", "APPROUVER_IA"}


def _serialize_poste(p: Poste, nb_comments: int = 0, nb_reactions: int = 0, derniers_comments: list = None) -> dict:
    return {
        "id": str(p.id),
        "titre": p.titre,
        "contenu": p.contenu,
        "auteurId": str(p.auteur_id) if p.auteur_id else None,
        "statut": p.statut,
        "approuverParId": str(p.approuver_par_id) if p.approuver_par_id else None,
        "estEpingle": p.est_epingle,
        "categorie": p.categorie,
        "imageUrl": p.image_url,
        "tags": p.tags or [],
        "nbCommentaires": nb_comments,
        "nbReactions": nb_reactions,
        "dateCreation": p.date_creation,
        "dateModification": p.date_modification,
        "styleMetadata": p.style_metadata or {},
        "derniersCommentaires": derniers_comments or [],
    }


class PosteService:
    def __init__(self, db: AsyncSession) -> None:
        self.db = db

    async def creer(self, data: PosteCreate, current_user: Utilisateur) -> dict:
        poste = Poste(
            titre=data.titre,
            contenu=data.contenu,
            auteur_id=current_user.id,
            statut="NOUVEAU",
            categorie=data.categorie,
            image_url=data.imageUrl,
            tags=data.tags,
            style_metadata=data.styleMetadata or {},
        )
        self.db.add(poste)
        await self.db.commit()
        await self.db.refresh(poste)
        result = _serialize_poste(poste)
        await events.broadcast_poste_created(str(poste.id), result)
        return result

    async def _batch_counts_and_comments(
        self,
        poste_ids: list[uuid.UUID],
        include_recent_comments: bool = False,
    ) -> tuple[dict[uuid.UUID, int], dict[uuid.UUID, int], dict[uuid.UUID, list[dict]]]:
        if not poste_ids:
            return {}, {}, {}

        # 1. Batch comments count
        c_stmt = (
            select(Commentaire.poste_id, func.count(Commentaire.id))
            .where(Commentaire.poste_id.in_(poste_ids))
            .group_by(Commentaire.poste_id)
        )
        c_res = await self.db.execute(c_stmt)
        c_counts = {poste_id: count for poste_id, count in c_res.all()}

        # 2. Batch reactions count
        r_stmt = (
            select(Reaction.poste_id, func.count(Reaction.id))
            .where(Reaction.poste_id.in_(poste_ids))
            .group_by(Reaction.poste_id)
        )
        r_res = await self.db.execute(r_stmt)
        r_counts = {poste_id: count for poste_id, count in r_res.all()}

        recent_comments: dict[uuid.UUID, list[dict]] = {}
        if include_recent_comments:
            cm_stmt = (
                select(Commentaire)
                .where(Commentaire.poste_id.in_(poste_ids))
                .order_by(Commentaire.date_creation.desc())
            )
            cm_res = (await self.db.execute(cm_stmt)).scalars().all()
            for c in cm_res:
                if c.poste_id not in recent_comments:
                    recent_comments[c.poste_id] = []
                if len(recent_comments[c.poste_id]) < 2:
                    recent_comments[c.poste_id].append({
                        "id": str(c.id),
                        "posteId": str(c.poste_id),
                        "auteurId": str(c.auteur_id) if c.auteur_id else None,
                        "contenu": c.contenu,
                        "dateCreation": c.date_creation,
                    })

        return c_counts, r_counts, recent_comments

    async def list_postes(self, page: int = 0, size: int = 20) -> tuple[list[dict], int]:
        q = select(Poste).order_by(Poste.est_epingle.desc(), Poste.date_creation.desc())
        total = (await self.db.execute(select(func.count(Poste.id)))).scalar_one()
        rows = (await self.db.execute(q.offset(page * size).limit(size))).scalars().all()
        poste_ids = [p.id for p in rows]
        c_counts, r_counts, _ = await self._batch_counts_and_comments(poste_ids)
        results = [
            _serialize_poste(p, c_counts.get(p.id, 0), r_counts.get(p.id, 0))
            for p in rows
        ]
        return results, total

    async def list_mes_postes(self, auteur_id: uuid.UUID, page: int = 0, size: int = 20) -> tuple[list[dict], int]:
        q = select(Poste).where(Poste.auteur_id == auteur_id).order_by(Poste.date_creation.desc())
        total = (await self.db.execute(select(func.count(Poste.id)).where(Poste.auteur_id == auteur_id))).scalar_one()
        rows = (await self.db.execute(q.offset(page * size).limit(size))).scalars().all()
        poste_ids = [p.id for p in rows]
        c_counts, r_counts, _ = await self._batch_counts_and_comments(poste_ids)
        results = [
            _serialize_poste(p, c_counts.get(p.id, 0), r_counts.get(p.id, 0))
            for p in rows
        ]
        return results, total

    async def list_postes_approuves(self, page: int = 0, size: int = 20, sort_by: str = "recommendation") -> tuple[list[dict], int]:
        comment_sub = select(func.count(Commentaire.id)).where(Commentaire.poste_id == Poste.id).scalar_subquery()
        reaction_sub = select(func.count(Reaction.id)).where(Reaction.poste_id == Poste.id).scalar_subquery()

        dialect = self.db.bind.dialect.name if self.db.bind else "sqlite"
        if dialect == "postgresql":
            age_hours = func.extract('epoch', func.now() - Poste.date_creation) / 3600.0
        else:
            age_hours = (func.julianday('now') - func.julianday(Poste.date_creation)) * 24.0

        score_expr = (reaction_sub * 2 + comment_sub * 5 + 10) / func.power(age_hours + 2.0, 1.2)

        if sort_by == "recommendation":
            order = [Poste.est_epingle.desc(), score_expr.desc(), Poste.date_creation.desc()]
        else:
            order = [Poste.est_epingle.desc(), Poste.date_creation.desc()]

        q = (
            select(Poste)
            .where(Poste.statut.in_(APPROVED_POSTE_STATUTS))
            .order_by(*order)
        )
        total = (
            await self.db.execute(
                select(func.count(Poste.id)).where(Poste.statut.in_(APPROVED_POSTE_STATUTS))
            )
        ).scalar_one()
        rows = (await self.db.execute(q.offset(page * size).limit(size))).scalars().all()
        poste_ids = [p.id for p in rows]
        c_counts, r_counts, recent_comments = await self._batch_counts_and_comments(poste_ids, include_recent_comments=True)
        results = [
            _serialize_poste(
                p,
                c_counts.get(p.id, 0),
                r_counts.get(p.id, 0),
                recent_comments.get(p.id, []),
            )
            for p in rows
        ]
        return results, total

    async def update_statut(self, poste_id: str | uuid.UUID, statut: str, current_user: Utilisateur) -> dict:
        if statut not in VALID_POSTE_STATUTS:
            raise HTTPException(status_code=400, detail="Statut de poste invalide")

        p = await self._get_or_404(poste_id)
        p.statut = statut

        if statut == "APPROUVER":
            p.approuver_par_id = current_user.id
        else:
            p.approuver_par_id = None

        await self.db.commit()
        await self.db.refresh(p)

        nb_c = (await self.db.execute(select(func.count(Commentaire.id)).where(Commentaire.poste_id == p.id))).scalar_one()
        nb_r = (await self.db.execute(select(func.count(Reaction.id)).where(Reaction.poste_id == p.id))).scalar_one()
        dashboard = await StatsService(self.db).get_dashboard()
        await events.broadcast_stats_updated(
            {
                "signalementsEnAttente": dashboard.get("signalementsEnAttente", 0),
                "evenementsAVenir": dashboard.get("evenementsAVenir", 0),
                "equipeAlertes": dashboard.get("equipeAlertes", 0),
                "moderationEnAttente": dashboard.get("moderationEnAttente", 0),
            }
        )
        
        result = _serialize_poste(p, nb_c, nb_r)
        await events.broadcast_poste_updated(str(p.id), result)
        return result

    async def get_by_id(self, poste_id: str | uuid.UUID) -> dict:
        p = await self._get_or_404(poste_id)
        nb_c = (await self.db.execute(select(func.count(Commentaire.id)).where(Commentaire.poste_id == p.id))).scalar_one()
        nb_r = (await self.db.execute(select(func.count(Reaction.id)).where(Reaction.poste_id == p.id))).scalar_one()
        return _serialize_poste(p, nb_c, nb_r)

    async def epingler(self, poste_id: str | uuid.UUID) -> dict:
        p = await self._get_or_404(poste_id)
        p.est_epingle = True
        await self.db.commit()
        await self.db.refresh(p)
        nb_c = (await self.db.execute(select(func.count(Commentaire.id)).where(Commentaire.poste_id == p.id))).scalar_one()
        nb_r = (await self.db.execute(select(func.count(Reaction.id)).where(Reaction.poste_id == p.id))).scalar_one()
        result = _serialize_poste(p, nb_c, nb_r)
        await events.broadcast_poste_updated(str(p.id), result)
        return result

    async def desepingler(self, poste_id: str | uuid.UUID) -> dict:
        p = await self._get_or_404(poste_id)
        p.est_epingle = False
        await self.db.commit()
        await self.db.refresh(p)
        nb_c = (await self.db.execute(select(func.count(Commentaire.id)).where(Commentaire.poste_id == p.id))).scalar_one()
        nb_r = (await self.db.execute(select(func.count(Reaction.id)).where(Reaction.poste_id == p.id))).scalar_one()
        result = _serialize_poste(p, nb_c, nb_r)
        await events.broadcast_poste_updated(str(p.id), result)
        return result

    async def supprimer(self, poste_id: str | uuid.UUID, current_user: Utilisateur) -> None:
        p = await self._get_or_404(poste_id)
        if str(p.auteur_id) != str(current_user.id) and current_user.type_utilisateur != "ADMIN_SYSTEME":
            raise HTTPException(status_code=403, detail="Permission insuffisante")
        pid_str = str(p.id)
        await self.db.delete(p)
        await self.db.commit()
        await events.broadcast_poste_deleted(pid_str)

    # ── Commentaires ──────────────────────────────────────────────────────────

    async def ajouter_commentaire(self, poste_id: str | uuid.UUID, data: CommentaireCreate, current_user: Utilisateur) -> dict:
        p = await self._get_or_404(poste_id)
        comment = Commentaire(
            poste_id=p.id,
            auteur_id=current_user.id,
            contenu=data.contenu,
        )
        self.db.add(comment)
        await self.db.commit()
        await self.db.refresh(comment)
        
        result = {
            "id": str(comment.id),
            "posteId": str(comment.poste_id),
            "auteurId": str(comment.auteur_id) if comment.auteur_id else None,
            "contenu": comment.contenu,
            "dateCreation": comment.date_creation,
        }
        
        # Broadcast event
        await events.broadcast_commentaire_created(str(p.id), result)
        return result

    async def list_commentaires(self, poste_id: str | uuid.UUID, page: int = 0, size: int = 50) -> tuple[list[dict], int]:
        p = await self._get_or_404(poste_id)
        q = select(Commentaire).where(Commentaire.poste_id == p.id)
        total = (await self.db.execute(select(func.count()).select_from(q.subquery()))).scalar_one()
        rows = (await self.db.execute(q.order_by(Commentaire.date_creation.asc()).offset(page * size).limit(size))).scalars().all()
        return [
            {
                "id": str(c.id),
                "posteId": str(c.poste_id),
                "auteurId": str(c.auteur_id) if c.auteur_id else None,
                "contenu": c.contenu,
                "dateCreation": c.date_creation,
            }
            for c in rows
        ], total

    async def supprimer_commentaire(self, commentaire_id: str | uuid.UUID, current_user: Utilisateur) -> None:
        commentaire_uuid = commentaire_id if isinstance(commentaire_id, uuid.UUID) else uuid.UUID(commentaire_id)
        c = (await self.db.execute(select(Commentaire).where(Commentaire.id == commentaire_uuid))).scalar_one_or_none()
        if not c:
            raise HTTPException(status_code=404, detail="Commentaire non trouvé")
        if str(c.auteur_id) != str(current_user.id) and current_user.type_utilisateur != "ADMIN_SYSTEME":
            raise HTTPException(status_code=403, detail="Permission insuffisante")
        await self.db.delete(c)
        await self.db.commit()

    # ── Réactions ─────────────────────────────────────────────────────────────

    async def ajouter_reaction(self, poste_id: str | uuid.UUID, data: ReactionCreate, current_user: Utilisateur) -> dict:
        p = await self._get_or_404(poste_id)
        reaction = Reaction(poste_id=p.id, auteur_id=current_user.id, type=data.type)
        self.db.add(reaction)
        await self.db.commit()
        await self.db.refresh(reaction)
        
        result = {
            "id": str(reaction.id),
            "posteId": str(reaction.poste_id),
            "auteurId": str(reaction.auteur_id) if reaction.auteur_id else None,
            "type": reaction.type,
            "dateCreation": reaction.date_creation,
        }
        
        # Broadcast event
        await events.broadcast_reaction_created(str(p.id), result)
        return result

    async def list_reactions(self, poste_id: str | uuid.UUID) -> list[dict]:
        p = await self._get_or_404(poste_id)
        rows = (await self.db.execute(select(Reaction).where(Reaction.poste_id == p.id))).scalars().all()
        return [
            {
                "id": str(r.id),
                "posteId": str(r.poste_id),
                "auteurId": str(r.auteur_id) if r.auteur_id else None,
                "type": r.type,
                "dateCreation": r.date_creation,
            }
            for r in rows
        ]

    async def supprimer_reaction(self, reaction_id: str | uuid.UUID, current_user: Utilisateur) -> None:
        reaction_uuid = reaction_id if isinstance(reaction_id, uuid.UUID) else uuid.UUID(reaction_id)
        r = (await self.db.execute(select(Reaction).where(Reaction.id == reaction_uuid))).scalar_one_or_none()
        if not r:
            raise HTTPException(status_code=404, detail="Réaction non trouvée")
        if str(r.auteur_id) != str(current_user.id) and current_user.type_utilisateur != "ADMIN_SYSTEME":
            raise HTTPException(status_code=403, detail="Permission insuffisante")
        
        poste_id = r.poste_id
        await self.db.delete(r)
        await self.db.commit()
        
        # Broadcast event
        await events.broadcast_reaction_removed(str(poste_id), {"reactionId": reaction_id})

    async def _get_or_404(self, poste_id: str | uuid.UUID) -> Poste:
        poste_uuid = poste_id if isinstance(poste_id, uuid.UUID) else uuid.UUID(poste_id)
        p = (await self.db.execute(select(Poste).where(Poste.id == poste_uuid))).scalar_one_or_none()
        if not p:
            raise HTTPException(status_code=404, detail="Poste non trouvé")
        return p
