"""
Servizio Virtual Box – CRUD, gestione assegnazioni utente e caselle reali.
"""

import uuid

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

from app.core.exceptions import ConflictError, ForbiddenError, NotFoundError
from app.models.mailbox import Mailbox
from app.models.user import User
from app.models.virtual_box import VirtualBox, VirtualBoxAssignment, VirtualBoxRule, virtual_box_mailboxes
from app.schemas.virtual_box import (
    AssignedUserResponse,
    VirtualBoxCreate,
    VirtualBoxResponse,
    VirtualBoxRuleResponse,
    VirtualBoxUpdate,
)


class VirtualBoxService:
    def __init__(self, db: AsyncSession) -> None:
        self.db = db

    # ─── CRUD VirtualBox ──────────────────────────────────────────────────────

    async def create(
        self,
        tenant_id: uuid.UUID,
        data: VirtualBoxCreate,
        created_by: uuid.UUID,
    ) -> VirtualBox:
        # Verifica unicità nome nel tenant
        existing = await self.db.execute(
            select(VirtualBox).where(
                VirtualBox.tenant_id == tenant_id,
                VirtualBox.name == data.name,
            )
        )
        if existing.scalar_one_or_none():
            raise ConflictError(f"Virtual Box con nome '{data.name}' già esistente")

        vbox = VirtualBox(
            tenant_id=tenant_id,
            name=data.name,
            description=data.description,
            label=data.label,
            created_by=created_by,
        )
        self.db.add(vbox)
        await self.db.flush()

        # Crea le regole
        for rule_data in data.rules:
            rule = VirtualBoxRule(
                virtual_box_id=vbox.id,
                field=rule_data.field,
                operator=rule_data.operator,
                value=rule_data.value,
                date_from=rule_data.date_from,
                date_to=rule_data.date_to,
            )
            self.db.add(rule)

        # Associa le caselle reali (INSERT diretto sulla tabella di associazione
        # per evitare MissingGreenlet con SQLAlchemy async)
        if data.mailbox_ids:
            valid_mailboxes = await self._load_mailboxes(data.mailbox_ids, tenant_id)
            if valid_mailboxes:
                await self.db.execute(
                    virtual_box_mailboxes.insert(),
                    [{"virtual_box_id": vbox.id, "mailbox_id": mb.id} for mb in valid_mailboxes],
                )

        await self.db.flush()
        return await self._load_full(vbox.id)

    async def list_vboxes(
        self,
        tenant_id: uuid.UUID,
        page: int = 1,
        page_size: int = 20,
        active_only: bool = False,
    ) -> tuple[list[VirtualBox], int]:
        query = select(VirtualBox).where(VirtualBox.tenant_id == tenant_id)
        if active_only:
            query = query.where(VirtualBox.is_active == True)
        query = query.order_by(VirtualBox.created_at.desc())

        count_result = await self.db.execute(
            select(func.count()).select_from(query.subquery())
        )
        total = count_result.scalar_one()

        query = query.offset((page - 1) * page_size).limit(page_size)
        query = query.options(
            selectinload(VirtualBox.rules),
            selectinload(VirtualBox.assignments),
            selectinload(VirtualBox.mailboxes),
        )
        result = await self.db.execute(query)
        items = list(result.scalars().all())
        return items, total

    async def get(self, vbox_id: uuid.UUID, tenant_id: uuid.UUID) -> VirtualBox:
        vbox = await self._load_full(vbox_id)
        if not vbox or vbox.tenant_id != tenant_id:
            raise NotFoundError("Virtual Box")
        return vbox

    async def update(
        self,
        vbox_id: uuid.UUID,
        tenant_id: uuid.UUID,
        data: VirtualBoxUpdate,
    ) -> VirtualBox:
        vbox = await self._load_full(vbox_id)
        if not vbox or vbox.tenant_id != tenant_id:
            raise NotFoundError("Virtual Box")

        if data.name is not None:
            # Verifica unicità nuovo nome
            existing = await self.db.execute(
                select(VirtualBox).where(
                    VirtualBox.tenant_id == tenant_id,
                    VirtualBox.name == data.name,
                    VirtualBox.id != vbox_id,
                )
            )
            if existing.scalar_one_or_none():
                raise ConflictError(f"Virtual Box con nome '{data.name}' già esistente")
            vbox.name = data.name

        if data.description is not None:
            vbox.description = data.description
        if data.label is not None:
            vbox.label = data.label
        if data.is_active is not None:
            vbox.is_active = data.is_active

        # Aggiorna le caselle associate se fornito (INSERT/DELETE diretti)
        if data.mailbox_ids is not None:
            await self.db.execute(
                virtual_box_mailboxes.delete().where(
                    virtual_box_mailboxes.c.virtual_box_id == vbox_id
                )
            )
            valid_mailboxes = await self._load_mailboxes(data.mailbox_ids, tenant_id)
            if valid_mailboxes:
                await self.db.execute(
                    virtual_box_mailboxes.insert(),
                    [{"virtual_box_id": vbox_id, "mailbox_id": mb.id} for mb in valid_mailboxes],
                )

        await self.db.flush()
        return await self._load_full(vbox_id)

    async def delete(self, vbox_id: uuid.UUID, tenant_id: uuid.UUID) -> None:
        vbox = await self.db.get(VirtualBox, vbox_id)
        if not vbox or vbox.tenant_id != tenant_id:
            raise NotFoundError("Virtual Box")
        await self.db.delete(vbox)

    # ─── Gestione Regole ─────────────────────────────────────────────────────

    async def replace_rules(
        self,
        vbox_id: uuid.UUID,
        tenant_id: uuid.UUID,
        rules_data: list,
    ) -> VirtualBox:
        """Sostituisce tutte le regole di una VBox."""
        vbox = await self.db.get(VirtualBox, vbox_id)
        if not vbox or vbox.tenant_id != tenant_id:
            raise NotFoundError("Virtual Box")

        # Rimuovi regole esistenti
        await self.db.execute(
            delete(VirtualBoxRule).where(VirtualBoxRule.virtual_box_id == vbox_id)
        )

        # Aggiungi nuove regole
        for rule_data in rules_data:
            rule = VirtualBoxRule(
                virtual_box_id=vbox_id,
                field=rule_data.field,
                operator=rule_data.operator,
                value=rule_data.value,
                date_from=rule_data.date_from,
                date_to=rule_data.date_to,
            )
            self.db.add(rule)

        await self.db.flush()
        return await self._load_full(vbox_id)

    # ─── Gestione Caselle Reali ───────────────────────────────────────────────

    async def set_mailboxes(
        self,
        vbox_id: uuid.UUID,
        tenant_id: uuid.UUID,
        mailbox_ids: list[uuid.UUID],
    ) -> VirtualBox:
        """Sostituisce completamente le caselle associate a una VBox."""
        vbox = await self._load_full(vbox_id)
        if not vbox or vbox.tenant_id != tenant_id:
            raise NotFoundError("Virtual Box")

        # Sostituzione completa con INSERT/DELETE diretti
        await self.db.execute(
            virtual_box_mailboxes.delete().where(
                virtual_box_mailboxes.c.virtual_box_id == vbox_id
            )
        )
        valid_mailboxes = await self._load_mailboxes(mailbox_ids, tenant_id)
        if valid_mailboxes:
            await self.db.execute(
                virtual_box_mailboxes.insert(),
                [{"virtual_box_id": vbox_id, "mailbox_id": mb.id} for mb in valid_mailboxes],
            )

        await self.db.flush()
        return await self._load_full(vbox_id)

    async def list_mailboxes(
        self,
        vbox_id: uuid.UUID,
        tenant_id: uuid.UUID,
    ) -> list[Mailbox]:
        """Restituisce le caselle associate a una VBox."""
        vbox = await self._load_full(vbox_id)
        if not vbox or vbox.tenant_id != tenant_id:
            raise NotFoundError("Virtual Box")
        return vbox.mailboxes or []

    # ─── Gestione Assegnazioni ────────────────────────────────────────────────

    async def assign_users(
        self,
        vbox_id: uuid.UUID,
        tenant_id: uuid.UUID,
        user_ids: list[uuid.UUID],
        assigned_by: uuid.UUID,
    ) -> list[VirtualBoxAssignment]:
        vbox = await self.db.get(VirtualBox, vbox_id)
        if not vbox or vbox.tenant_id != tenant_id:
            raise NotFoundError("Virtual Box")

        # Recupera assegnazioni esistenti
        existing_result = await self.db.execute(
            select(VirtualBoxAssignment.user_id).where(
                VirtualBoxAssignment.virtual_box_id == vbox_id
            )
        )
        existing_user_ids = {row[0] for row in existing_result.all()}

        new_assignments = []
        for user_id in user_ids:
            if user_id not in existing_user_ids:
                # Verifica che l'utente esista e appartenga al tenant
                user = await self.db.get(User, user_id)
                if not user or user.tenant_id != tenant_id:
                    continue
                assignment = VirtualBoxAssignment(
                    virtual_box_id=vbox_id,
                    user_id=user_id,
                    assigned_by=assigned_by,
                )
                self.db.add(assignment)
                new_assignments.append(assignment)

        await self.db.flush()
        return new_assignments

    async def unassign_user(
        self,
        vbox_id: uuid.UUID,
        tenant_id: uuid.UUID,
        user_id: uuid.UUID,
    ) -> None:
        vbox = await self.db.get(VirtualBox, vbox_id)
        if not vbox or vbox.tenant_id != tenant_id:
            raise NotFoundError("Virtual Box")

        result = await self.db.execute(
            delete(VirtualBoxAssignment).where(
                VirtualBoxAssignment.virtual_box_id == vbox_id,
                VirtualBoxAssignment.user_id == user_id,
            )
        )
        if result.rowcount == 0:
            raise NotFoundError("assegnazione")

    async def list_assigned_users(
        self,
        vbox_id: uuid.UUID,
        tenant_id: uuid.UUID,
    ) -> list[dict]:
        vbox = await self.db.get(VirtualBox, vbox_id)
        if not vbox or vbox.tenant_id != tenant_id:
            raise NotFoundError("Virtual Box")

        result = await self.db.execute(
            select(VirtualBoxAssignment, User)
            .join(User, VirtualBoxAssignment.user_id == User.id)
            .where(VirtualBoxAssignment.virtual_box_id == vbox_id)
        )
        return [
            {
                "user_id": assignment.user_id,
                "user_email": user.email,
                "user_full_name": user.full_name,
                "assigned_at": assignment.assigned_at,
            }
            for assignment, user in result.all()
        ]

    async def list_user_vboxes(
        self,
        user_id: uuid.UUID,
        tenant_id: uuid.UUID,
    ) -> list[VirtualBox]:
        """Restituisce le VBox assegnate a un utente specifico."""
        result = await self.db.execute(
            select(VirtualBox)
            .join(VirtualBoxAssignment, VirtualBox.id == VirtualBoxAssignment.virtual_box_id)
            .where(
                VirtualBoxAssignment.user_id == user_id,
                VirtualBox.tenant_id == tenant_id,
                VirtualBox.is_active == True,
            )
            .options(
                selectinload(VirtualBox.rules),
                selectinload(VirtualBox.mailboxes),
                selectinload(VirtualBox.assignments),  # necessario per _to_response()
            )
        )
        return list(result.scalars().all())

    async def get_user_sendable_mailboxes(
        self,
        user_id: uuid.UUID,
        tenant_id: uuid.UUID,
    ) -> list[Mailbox]:
        """
        Restituisce le caselle PEC da cui l'utente può inviare tramite VBox.

        Aggregazione delle caselle associate a tutte le VBox attive a cui
        l'utente è assegnato. Filtra solo caselle in stato 'active'.
        """
        result = await self.db.execute(
            select(Mailbox)
            .join(virtual_box_mailboxes, Mailbox.id == virtual_box_mailboxes.c.mailbox_id)
            .join(VirtualBox, virtual_box_mailboxes.c.virtual_box_id == VirtualBox.id)
            .join(VirtualBoxAssignment, VirtualBox.id == VirtualBoxAssignment.virtual_box_id)
            .where(
                VirtualBoxAssignment.user_id == user_id,
                VirtualBox.tenant_id == tenant_id,
                VirtualBox.is_active == True,
                Mailbox.status == "active",
            )
            .distinct()
            .order_by(Mailbox.email_address)
        )
        return list(result.scalars().all())

    # ─── Private ─────────────────────────────────────────────────────────────

    async def _load_full(self, vbox_id: uuid.UUID) -> VirtualBox | None:
        result = await self.db.execute(
            select(VirtualBox)
            .where(VirtualBox.id == vbox_id)
            .options(
                selectinload(VirtualBox.rules),
                selectinload(VirtualBox.assignments),
                selectinload(VirtualBox.mailboxes),
            )
        )
        return result.scalar_one_or_none()

    async def _load_mailboxes(
        self,
        mailbox_ids: list[uuid.UUID],
        tenant_id: uuid.UUID,
    ) -> list[Mailbox]:
        """Carica le mailbox dal DB, filtrando per tenant."""
        if not mailbox_ids:
            return []
        result = await self.db.execute(
            select(Mailbox).where(
                Mailbox.id.in_(mailbox_ids),
                Mailbox.tenant_id == tenant_id,
            )
        )
        return list(result.scalars().all())
