import uuid
from datetime import date, datetime, timezone

from fastapi import HTTPException
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.models.calendrier import Calendrier
from app.models.inscription_evenement import InscriptionEvenement
from app.models.utilisateur import Utilisateur
from app.realtime import events as ws_events
from app.schemas.calendrier import CalendrierCreate, CalendrierUpdate


def _serialize(c: Calendrier, inscrit: bool = False) -> dict:
    return {
        "id": str(c.id),
        "titre": c.titre,
        "description": c.description,
        "type": c.type,
        "statut": c.statut,
        "dateDebut": c.date_debut,
        "dateFin": c.date_fin,
        "lieu": c.lieu,
        "estEnLigne": c.est_en_ligne,
        "lienEnLigne": c.lien_en_ligne,
        "nbMaxParticipants": c.nb_max_participants,
        "nbParticipantsActuels": c.nb_participants_actuels,
        "imageUrl": c.image_url,
        "tags": c.tags or [],
        "publicCible": c.public_cible or [],
        "organisateurId": str(c.organisateur_id) if c.organisateur_id else None,
        "estInscrit": inscrit,
        "dateCreation": c.date_creation,
        "dateModification": c.date_modification,
    }


class CalendrierService:
    def __init__(self, db: AsyncSession) -> None:
        self.db = db

    async def creer(self, user_id: uuid.UUID, data: CalendrierCreate) -> dict:
        cal = Calendrier(
            titre=data.titre,
            description=data.description,
            type=data.type,
            statut="PUBLIE",
            date_debut=data.dateDebut,
            date_fin=data.dateFin,
            lieu=data.lieu,
            est_en_ligne=data.estEnLigne,
            lien_en_ligne=data.lienEnLigne,
            nb_max_participants=data.nbMaxParticipants,
            image_url=data.imageUrl,
            tags=data.tags,
            public_cible=data.publicCible,
            organisateur_id=user_id,
        )
        self.db.add(cal)
        await self.db.commit()
        await self.db.refresh(cal)
        result = _serialize(cal)
        await ws_events.broadcast_event_created(result)
        return result

    async def list_all(self, user_id: uuid.UUID, page: int = 0, size: int = 20) -> tuple[list[dict], int]:
        q = select(Calendrier).where(Calendrier.statut != "ANNULE")
        total = (await self.db.execute(select(func.count()).select_from(q.subquery()))).scalar_one()
        rows = (
            await self.db.execute(
                q.order_by(Calendrier.date_debut.asc()).offset(page * size).limit(size)
            )
        ).scalars().all()

        user_inscriptions = await self._get_user_inscriptions(user_id)

        return [_serialize(c, str(c.id) in user_inscriptions) for c in rows], total

    async def get_by_id(self, cal_id: str, current_user: Utilisateur) -> dict:
        cal = await self._get_or_404(cal_id)
        inscrit = await self._is_inscrit(current_user.id, cal.id)
        return _serialize(cal, inscrit)

    async def get_by_date(self, user_id: uuid.UUID, target: date) -> list[dict]:
        rows = (
            await self.db.execute(
                select(Calendrier).where(
                    func.date(Calendrier.date_debut) == target,
                    Calendrier.statut != "ANNULE",
                )
            )
        ).scalars().all()
        user_inscriptions = await self._get_user_inscriptions(user_id)
        return [_serialize(c, str(c.id) in user_inscriptions) for c in rows]

    async def get_range(self, user_id: uuid.UUID, date_debut: date, date_fin: date) -> list[dict]:
        rows = (
            await self.db.execute(
                select(Calendrier).where(
                    func.date(Calendrier.date_debut) >= date_debut,
                    func.date(Calendrier.date_debut) <= date_fin,
                    Calendrier.statut != "ANNULE",
                )
            )
        ).scalars().all()
        user_inscriptions = await self._get_user_inscriptions(user_id)
        return [_serialize(c, str(c.id) in user_inscriptions) for c in rows]

    async def update(self, cal_id: str, data: CalendrierUpdate, current_user: Utilisateur) -> dict:
        cal = await self._get_or_404(cal_id)
        if str(cal.organisateur_id) != str(current_user.id) and current_user.type_utilisateur not in (
            "ADMIN_SYSTEME",
            "GESTIONNAIRE",
        ):
            raise HTTPException(status_code=403, detail="Permission insuffisante")

        for field, value in data.model_dump(exclude_none=True).items():
            snake = _to_snake(field)
            if hasattr(cal, snake):
                setattr(cal, snake, value)

        await self.db.commit()
        await self.db.refresh(cal)
        result = _serialize(cal)
        await ws_events.broadcast_event_updated(result)
        return result

    async def delete(self, cal_id: str, current_user: Utilisateur) -> None:
        cal = await self._get_or_404(cal_id)
        if str(cal.organisateur_id) != str(current_user.id) and current_user.type_utilisateur not in (
            "ADMIN_SYSTEME",
            "GESTIONNAIRE",
        ):
            raise HTTPException(status_code=403, detail="Permission insuffisante")
        await self.db.delete(cal)
        await self.db.commit()
        await ws_events.broadcast_event_cancelled({"id": cal_id})

    async def inscrire(self, cal_id: str, current_user: Utilisateur) -> dict:
        cal = await self._get_or_404(cal_id)
        if cal.nb_max_participants and cal.nb_participants_actuels >= cal.nb_max_participants:
            raise HTTPException(status_code=409, detail="Événement complet")

        existing = await self._is_inscrit(current_user.id, cal.id)
        if existing:
            raise HTTPException(status_code=409, detail="Déjà inscrit")

        inscription = InscriptionEvenement(
            calendrier_id=cal.id,
            utilisateur_id=current_user.id,
            statut="INSCRIT",
        )
        self.db.add(inscription)
        cal.nb_participants_actuels += 1
        await self.db.commit()
        await self.db.refresh(cal)
        await ws_events.broadcast_event_updated(_serialize(cal))
        return {"message": "Inscription réussie", "calendrierId": cal_id}

    async def desinscrire(self, cal_id: str, current_user: Utilisateur) -> dict:
        cal = await self._get_or_404(cal_id)
        inscription = (
            await self.db.execute(
                select(InscriptionEvenement).where(
                    InscriptionEvenement.calendrier_id == cal.id,
                    InscriptionEvenement.utilisateur_id == current_user.id,
                )
            )
        ).scalar_one_or_none()

        if not inscription:
            raise HTTPException(status_code=404, detail="Inscription non trouvée")

        await self.db.delete(inscription)
        if cal.nb_participants_actuels > 0:
            cal.nb_participants_actuels -= 1
        await self.db.commit()
        await self.db.refresh(cal)
        await ws_events.broadcast_event_updated(_serialize(cal))
        return {"message": "Désinscription réussie"}

    async def list_participants(
        self, cal_id: str, page: int = 0, size: int = 20
    ) -> tuple[list[dict], int]:
        """List all participants for an event (admin view)."""
        cal = await self._get_or_404(cal_id)
        
        q = select(InscriptionEvenement).options(
            selectinload(InscriptionEvenement.utilisateur)
        ).where(
            InscriptionEvenement.calendrier_id == cal.id
        )
        total = (await self.db.execute(select(func.count()).select_from(q.subquery()))).scalar_one()
        
        rows = (
            await self.db.execute(
                q.order_by(InscriptionEvenement.date_inscription.desc())
                .offset(page * size)
                .limit(size)
            )
        ).scalars().all()
        
        return [self._serialize_participant(r) for r in rows], total

    async def update_participant_statut(
        self, participant_id: str, nouveau_statut: str
    ) -> dict:
        """Update participant status."""
        inscription = (
            await self.db.execute(
                select(InscriptionEvenement)
                .options(selectinload(InscriptionEvenement.utilisateur))
                .where(InscriptionEvenement.id == uuid.UUID(participant_id))
            )
        ).scalar_one_or_none()
        
        if not inscription:
            raise HTTPException(status_code=404, detail="Participant non trouvé")
        
        inscription.statut = nouveau_statut
        await self.db.commit()
        await self.db.refresh(inscription)
        
        return self._serialize_participant(inscription)

    def _serialize_participant(self, inscription: InscriptionEvenement) -> dict:
        """Serialize participant for API response."""
        return {
            "id": str(inscription.id),
            "eventId": str(inscription.calendrier_id),
            "userId": str(inscription.utilisateur_id),
            "userName": inscription.utilisateur.nom if inscription.utilisateur else None,
            "userEmail": inscription.utilisateur.email if inscription.utilisateur else None,
            "statut": inscription.statut,
            "dateInscription": inscription.date_inscription,
            "dateModification": inscription.date_inscription,
        }

    async def _get_user_inscriptions(self, user_id: uuid.UUID) -> set[str]:
        rows = (
            await self.db.execute(
                select(InscriptionEvenement.calendrier_id).where(
                    InscriptionEvenement.utilisateur_id == user_id
                )
            )
        ).scalars().all()
        return {str(r) for r in rows}

    async def _is_inscrit(self, user_id: uuid.UUID, cal_id: uuid.UUID) -> bool:
        r = (
            await self.db.execute(
                select(InscriptionEvenement).where(
                    InscriptionEvenement.utilisateur_id == user_id,
                    InscriptionEvenement.calendrier_id == cal_id,
                )
            )
        ).scalar_one_or_none()
        return r is not None

    async def _get_or_404(self, cal_id: str) -> Calendrier:
        c = (
            await self.db.execute(select(Calendrier).where(Calendrier.id == uuid.UUID(cal_id)))
        ).scalar_one_or_none()
        if not c:
            raise HTTPException(status_code=404, detail="Événement non trouvé")
        return c


def _to_snake(camel: str) -> str:
    """
    Very simple camelCase to snake_case for CalendrierUpdate field mapping.
    Only handles known fields.
    """
    mapping = {
        "titre": "titre",
        "description": "description",
        "type": "type",
        "statut": "statut",
        "dateDebut": "date_debut",
        "dateFin": "date_fin",
        "lieu": "lieu",
        "estEnLigne": "est_en_ligne",
        "lienEnLigne": "lien_en_ligne",
        "nbMaxParticipants": "nb_max_participants",
        "imageUrl": "image_url",
        "tags": "tags",
        "publicCible": "public_cible",
    }
    return mapping.get(camel, camel)
