"""
Service per la gestione dei Fascicoli (fascicolazione pratiche).
"""

import uuid
from datetime import datetime

from sqlalchemy import delete, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.exceptions import ConflictError, ForbiddenError, NotFoundError
from app.models.fascicolo import Fascicolo, FascicoloMessage
from app.models.message import Message
from app.models.user import User
from app.schemas.fascicolo import FascicoloCreate, FascicoloUpdate


class FascicoloService:
    def __init__(self, db: AsyncSession):
        self.db = db

    # ─── CRUD Fascicolo ───────────────────────────────────────────────────────

    async def list_fascicoli(
        self,
        tenant_id: uuid.UUID,
        stato: str | None = None,
        responsabile_id: uuid.UUID | None = None,
        search: str | None = None,
    ) -> list[tuple[Fascicolo, int]]:
        """
        Restituisce lista di (Fascicolo, message_count) con filtri opzionali.
        """
        # Subquery per il conteggio messaggi
        count_sub = (
            select(
                FascicoloMessage.fascicolo_id,
                func.count(FascicoloMessage.message_id).label("cnt"),
            )
            .group_by(FascicoloMessage.fascicolo_id)
            .subquery()
        )

        stmt = (
            select(Fascicolo, func.coalesce(count_sub.c.cnt, 0).label("message_count"))
            .outerjoin(count_sub, Fascicolo.id == count_sub.c.fascicolo_id)
            .where(Fascicolo.tenant_id == tenant_id)
            .order_by(Fascicolo.updated_at.desc())
        )

        if stato:
            stmt = stmt.where(Fascicolo.stato == stato)
        if responsabile_id:
            stmt = stmt.where(Fascicolo.responsabile_id == responsabile_id)
        if search:
            pattern = f"%{search}%"
            stmt = stmt.where(
                Fascicolo.titolo.ilike(pattern)
                | Fascicolo.numero_pratica.ilike(pattern)
                | Fascicolo.categoria.ilike(pattern)
            )

        result = await self.db.execute(stmt)
        return list(result.all())

    async def get_fascicolo(
        self, tenant_id: uuid.UUID, fascicolo_id: uuid.UUID
    ) -> tuple[Fascicolo, int]:
        """Restituisce (Fascicolo, message_count) o solleva NotFoundError."""
        count_sub = (
            select(
                FascicoloMessage.fascicolo_id,
                func.count(FascicoloMessage.message_id).label("cnt"),
            )
            .group_by(FascicoloMessage.fascicolo_id)
            .subquery()
        )

        result = await self.db.execute(
            select(Fascicolo, func.coalesce(count_sub.c.cnt, 0).label("message_count"))
            .outerjoin(count_sub, Fascicolo.id == count_sub.c.fascicolo_id)
            .where(
                Fascicolo.id == fascicolo_id,
                Fascicolo.tenant_id == tenant_id,
            )
        )
        row = result.one_or_none()
        if not row:
            raise NotFoundError(f"Fascicolo {fascicolo_id} non trovato")
        return row

    async def create_fascicolo(
        self,
        tenant_id: uuid.UUID,
        data: FascicoloCreate,
        created_by: uuid.UUID,
    ) -> tuple[Fascicolo, int]:
        fascicolo = Fascicolo(
            id=uuid.uuid4(),
            tenant_id=tenant_id,
            titolo=data.titolo,
            numero_pratica=data.numero_pratica,
            stato=data.stato or "aperto",
            categoria=data.categoria,
            responsabile_id=data.responsabile_id,
            scadenza=data.scadenza,
            note=data.note,
            created_by=created_by,
        )
        self.db.add(fascicolo)
        await self.db.commit()
        await self.db.refresh(fascicolo)
        return fascicolo, 0

    async def update_fascicolo(
        self,
        tenant_id: uuid.UUID,
        fascicolo_id: uuid.UUID,
        data: FascicoloUpdate,
        current_user_id: uuid.UUID,
        is_admin: bool,
    ) -> tuple[Fascicolo, int]:
        fascicolo, count = await self.get_fascicolo(tenant_id, fascicolo_id)

        # Solo admin o il creatore possono modificare
        if not is_admin and fascicolo.created_by != current_user_id:
            raise ForbiddenError("Solo il creatore o un amministratore puo' modificare questo fascicolo")

        if data.titolo is not None:
            fascicolo.titolo = data.titolo
        if data.numero_pratica is not None:
            fascicolo.numero_pratica = data.numero_pratica
        if data.stato is not None:
            fascicolo.stato = data.stato
        if data.categoria is not None:
            fascicolo.categoria = data.categoria
        if data.responsabile_id is not None:
            fascicolo.responsabile_id = data.responsabile_id
        if data.scadenza is not None:
            fascicolo.scadenza = data.scadenza
        if data.note is not None:
            fascicolo.note = data.note

        fascicolo.updated_at = datetime.utcnow()
        await self.db.commit()
        await self.db.refresh(fascicolo)
        return fascicolo, count

    async def delete_fascicolo(
        self,
        tenant_id: uuid.UUID,
        fascicolo_id: uuid.UUID,
    ) -> None:
        fascicolo, _ = await self.get_fascicolo(tenant_id, fascicolo_id)
        await self.db.delete(fascicolo)
        await self.db.commit()

    # ─── Messaggi nel fascicolo ───────────────────────────────────────────────

    async def get_fascicolo_messages(
        self,
        tenant_id: uuid.UUID,
        fascicolo_id: uuid.UUID,
    ) -> list[tuple[Message, datetime]]:
        """
        Restituisce lista di (Message, added_at) ordinati per added_at desc.
        """
        # Verifica fascicolo appartiene al tenant
        await self.get_fascicolo(tenant_id, fascicolo_id)

        result = await self.db.execute(
            select(Message, FascicoloMessage.added_at)
            .join(FascicoloMessage, Message.id == FascicoloMessage.message_id)
            .where(
                FascicoloMessage.fascicolo_id == fascicolo_id,
                Message.tenant_id == tenant_id,
            )
            .order_by(FascicoloMessage.added_at.desc())
        )
        return list(result.all())

    async def add_messages(
        self,
        tenant_id: uuid.UUID,
        fascicolo_id: uuid.UUID,
        message_ids: list[uuid.UUID],
        added_by: uuid.UUID,
    ) -> int:
        """
        Aggiunge messaggi al fascicolo. Ignora duplicati e messaggi non del tenant.
        Restituisce il numero di messaggi aggiunti.
        """
        await self.get_fascicolo(tenant_id, fascicolo_id)

        # Verifica che i messaggi appartengano al tenant
        msg_result = await self.db.execute(
            select(Message.id).where(
                Message.id.in_(message_ids),
                Message.tenant_id == tenant_id,
            )
        )
        valid_ids = list(msg_result.scalars().all())

        if not valid_ids:
            return 0

        # Carica associazioni esistenti per evitare duplicati
        existing_result = await self.db.execute(
            select(FascicoloMessage.message_id).where(
                FascicoloMessage.fascicolo_id == fascicolo_id,
                FascicoloMessage.message_id.in_(valid_ids),
            )
        )
        existing_ids = set(existing_result.scalars().all())

        added = 0
        for msg_id in valid_ids:
            if msg_id not in existing_ids:
                self.db.add(
                    FascicoloMessage(
                        fascicolo_id=fascicolo_id,
                        message_id=msg_id,
                        added_by=added_by,
                    )
                )
                added += 1

        if added:
            await self.db.commit()

        return added

    async def remove_messages(
        self,
        tenant_id: uuid.UUID,
        fascicolo_id: uuid.UUID,
        message_ids: list[uuid.UUID],
    ) -> int:
        """Rimuove messaggi dal fascicolo. Restituisce il numero rimosso."""
        await self.get_fascicolo(tenant_id, fascicolo_id)

        result = await self.db.execute(
            delete(FascicoloMessage).where(
                FascicoloMessage.fascicolo_id == fascicolo_id,
                FascicoloMessage.message_id.in_(message_ids),
            ).returning(FascicoloMessage.message_id)
        )
        removed = len(result.fetchall())
        if removed:
            await self.db.commit()
        return removed

    # ─── Fascicoli di un messaggio ────────────────────────────────────────────

    async def get_message_fascicoli(
        self,
        tenant_id: uuid.UUID,
        message_id: uuid.UUID,
    ) -> list[Fascicolo]:
        """Restituisce i fascicoli a cui appartiene un messaggio."""
        result = await self.db.execute(
            select(Fascicolo)
            .join(FascicoloMessage, Fascicolo.id == FascicoloMessage.fascicolo_id)
            .where(
                FascicoloMessage.message_id == message_id,
                Fascicolo.tenant_id == tenant_id,
            )
            .order_by(Fascicolo.titolo)
        )
        return list(result.scalars().all())
