"""
Mail entrant — API views pour la configuration IMAP, les sources et l'inbox.

Routes:
  GET/PUT    /tenant/mail/imap/            → config IMAP (singleton par tenant)
  POST       /tenant/mail/imap/test/       → tester la connexion IMAP
  POST       /tenant/mail/imap/poll/       → déclencher un poll immédiat
  GET        /tenant/mail/imap/status/     → statut du service de polling (heartbeat)
  GET/POST   /tenant/mail/sources/         → lister / créer une source
  PATCH/DEL  /tenant/mail/sources/<id>/    → modifier / supprimer
  POST       /tenant/mail/sources/<id>/toggle/   → activer/désactiver
  GET        /tenant/mail/inbox/           → liste mails interceptés
  GET        /tenant/mail/inbox/<id>/      → détail + pièces jointes
  POST       /tenant/mail/inbox/<id>/dispatch/   → dispatcher au responsable pays
  POST       /tenant/mail/inbox/<id>/reject/     → rejeter
  POST       /tenant/mail/inbox/<id>/restore/    → remettre en attente
  GET        /tenant/mail/country-managers/      → liste des responsables pays du tenant
"""

import imaplib
import json
import logging
import mimetypes
import os
import socket
import threading
import time

from django.contrib.auth import get_user_model
from django.http import FileResponse, Http404, StreamingHttpResponse
from django.utils import timezone
from django.views.decorators.http import condition

logger = logging.getLogger(__name__)
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from accounts.models import (
    Membership, Role,
    MailInboxConfig, MailSource, IncomingMail, MailAttachment,
)

User = get_user_model()


# ─────────────────────────────────────────────────────────────
# Helpers partagés
# ─────────────────────────────────────────────────────────────

def _get_active_membership(user):
    qs = (
        Membership.objects.filter(user=user, status=Membership.Status.ACTIVE)
        .select_related("tenant")
        .prefetch_related("roles")
    )
    return qs.filter(is_owner=True).first() or qs.first()


def _is_tenant_admin(membership):
    if not membership:
        return False
    if membership.is_owner:
        return True
    role_ids = set(membership.roles.values_list("id", flat=True))
    return (Role.ADMIN in role_ids) or (Role.APPROVER in role_ids)


def _imap_config_payload(cfg):
    return {
        "id": cfg.id,
        "imap_host": cfg.imap_host,
        "imap_port": cfg.imap_port,
        "imap_user": cfg.imap_user,
        "imap_password_set": bool(cfg.imap_password),
        "use_ssl": cfg.use_ssl,
        "mailbox": cfg.mailbox,
        "is_active": cfg.is_active,
        "last_polled_at": cfg.last_polled_at,
        "last_error": cfg.last_error,
        "updated_at": cfg.updated_at,
    }


def _source_payload(src):
    return {
        "id": src.id,
        "client_name": src.client_name,
        "email_or_domain": src.email_or_domain,
        "is_domain": src.is_domain,
        "notes": src.notes,
        "is_active": src.is_active,
        "created_at": src.created_at,
        "updated_at": src.updated_at,
    }


def _mail_list_payload(mail):
    return {
        "id": mail.id,
        "from_email": mail.from_email,
        "from_name": mail.from_name,
        "subject": mail.subject,
        "received_at": mail.received_at,
        "status": mail.status,
        "mail_source": {
            "id": mail.mail_source_id,
            "client_name": mail.mail_source.client_name if mail.mail_source else "",
            "email_or_domain": mail.mail_source.email_or_domain if mail.mail_source else "",
        } if mail.mail_source_id else None,
        "assigned_to": {
            "id": mail.assigned_to_id,
            "name": str(mail.assigned_to),
        } if mail.assigned_to_id else None,
        "assigned_country": mail.assigned_country,
        "attachments_count": mail.attachments.count(),
    }


def _mail_detail_payload(mail):
    payload = _mail_list_payload(mail)
    payload.update({
        "body_text": mail.body_text,
        "body_html": mail.body_html,
        "dispatch_note": mail.dispatch_note,
        "dispatched_at": mail.dispatched_at,
        "dispatched_by": {
            "id": mail.dispatched_by_id,
            "name": str(mail.dispatched_by),
        } if mail.dispatched_by_id else None,
        "accepted_at": mail.accepted_at,
        "accepted_by": {
            "id": mail.accepted_by_id,
            "name": str(mail.accepted_by),
        } if mail.accepted_by_id else None,
        "case": mail.case_id,
        "attachments": [
            {
                "id": a.id,
                "filename": a.filename,
                "content_type": a.content_type,
                "size": a.size,
                "url": a.file.url if a.file else None,
                "download_url": f"/tenant/mail/attachments/{a.id}/download/",
            }
            for a in mail.attachments.all()
        ],
    })
    return payload


# ─────────────────────────────────────────────────────────────
# IMAP Config
# ─────────────────────────────────────────────────────────────

@api_view(["GET", "PUT"])
@permission_classes([IsAuthenticated])
def mail_imap_config(request):
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)
    if not _is_tenant_admin(actor_m):
        return Response({"detail": "Not allowed."}, status=status.HTTP_403_FORBIDDEN)

    tenant = actor_m.tenant

    if request.method == "GET":
        try:
            cfg = MailInboxConfig.objects.get(tenant=tenant)
            return Response(_imap_config_payload(cfg))
        except MailInboxConfig.DoesNotExist:
            return Response(None)

    # PUT — créer ou mettre à jour
    data = request.data or {}
    cfg, _ = MailInboxConfig.objects.get_or_create(tenant=tenant)

    cfg.imap_host = (data.get("imap_host") or "").strip()
    cfg.imap_port = int(data.get("imap_port") or 993)
    cfg.imap_user = (data.get("imap_user") or "").strip()
    cfg.use_ssl = bool(data.get("use_ssl", True))
    cfg.mailbox = (data.get("mailbox") or "INBOX").strip() or "INBOX"
    cfg.is_active = bool(data.get("is_active", True))

    if "imap_password" in data and data["imap_password"]:
        cfg.imap_password = data["imap_password"]

    if not cfg.imap_host or not cfg.imap_user:
        return Response({"detail": "imap_host et imap_user sont requis."}, status=400)

    cfg.save()
    return Response(_imap_config_payload(cfg))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def mail_imap_test(request):
    """Teste la connexion IMAP avec les identifiants fournis."""
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)
    if not _is_tenant_admin(actor_m):
        return Response({"detail": "Not allowed."}, status=status.HTTP_403_FORBIDDEN)

    data = request.data or {}
    host = (data.get("imap_host") or "").strip()
    port = int(data.get("imap_port") or 993)
    user = (data.get("imap_user") or "").strip()
    password = data.get("imap_password") or ""
    use_ssl = bool(data.get("use_ssl", True))
    mailbox = (data.get("mailbox") or "INBOX").strip()

    if not host or not user or not password:
        return Response({"detail": "Hôte, utilisateur et mot de passe requis."}, status=400)

    # Utiliser le mot de passe enregistré si non fourni
    if not password:
        try:
            cfg = MailInboxConfig.objects.get(tenant=actor_m.tenant)
            password = cfg.imap_password
        except MailInboxConfig.DoesNotExist:
            pass

    try:
        if use_ssl:
            imap = imaplib.IMAP4_SSL(host, port)
        else:
            imap = imaplib.IMAP4(host, port)
        imap.login(user, password)
        status_code, data_resp = imap.select(mailbox, readonly=True)
        msg_count = int(data_resp[0]) if status_code == "OK" and data_resp[0] else 0
        imap.logout()
        return Response({
            "success": True,
            "message": f"Connexion réussie. {msg_count} message(s) dans {mailbox}.",
        })
    except imaplib.IMAP4.error as e:
        return Response({"success": False, "message": f"Erreur IMAP : {e}"}, status=400)
    except socket.gaierror:
        return Response({"success": False, "message": "Hôte IMAP introuvable."}, status=400)
    except Exception as e:
        return Response({"success": False, "message": str(e)}, status=400)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def mail_imap_poll(request):
    """Déclenche un poll IMAP immédiat pour le tenant courant."""
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)
    if not _is_tenant_admin(actor_m):
        return Response({"detail": "Not allowed."}, status=status.HTTP_403_FORBIDDEN)

    try:
        cfg = MailInboxConfig.objects.get(tenant=actor_m.tenant, is_active=True)
    except MailInboxConfig.DoesNotExist:
        return Response({"detail": "Aucune configuration IMAP active."}, status=404)

    from django.core.management import call_command
    try:
        call_command("poll_mail", tenant_id=cfg.tenant_id)
        cfg.refresh_from_db()
        return Response({
            "success": True,
            "last_polled_at": cfg.last_polled_at,
            "last_error": cfg.last_error,
        })
    except Exception as e:
        return Response({"success": False, "message": str(e)}, status=500)


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def mail_cron_status(request):
    """
    Retourne l'état du service de polling pour ce tenant.

    health:
      "unconfigured" — aucune config IMAP
      "inactive"     — config présente mais is_active=False (arrêté par le tenant)
      "healthy"      — dernier passage < 10 min
      "delayed"      — dernier passage entre 10 et 20 min (1–2 cycles manqués)
      "stale"        — dernier passage > 20 min ou jamais (cron potentiellement arrêté)
    """
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)

    try:
        cfg = MailInboxConfig.objects.get(tenant=actor_m.tenant)
    except MailInboxConfig.DoesNotExist:
        return Response({
            "health": "unconfigured",
            "health_label": "Non configuré",
            "is_active": False,
            "last_run": None,
            "last_error": "",
            "minutes_since_last_run": None,
            "cron_interval_minutes": 5,
        })

    if not cfg.is_active:
        return Response({
            "health": "inactive",
            "health_label": "Arrêté",
            "is_active": False,
            "last_run": cfg.last_polled_at,
            "last_error": cfg.last_error,
            "minutes_since_last_run": _minutes_ago(cfg.last_polled_at),
            "cron_interval_minutes": 5,
        })

    minutes = _minutes_ago(cfg.last_polled_at)

    if minutes is None:
        health = "stale"
        label = "En attente du premier passage"
    elif cfg.last_error:
        health = "error"
        short_err = cfg.last_error[:80]
        label = f"Erreur IMAP — {short_err}"
    elif minutes < 10:
        health = "healthy"
        label = f"Actif — dernier passage il y a {minutes} min"
    elif minutes < 20:
        health = "delayed"
        label = f"En retard — dernier passage il y a {minutes} min"
    else:
        health = "stale"
        label = f"Inactif — dernier passage il y a {minutes} min"

    return Response({
        "health": health,
        "health_label": label,
        "is_active": cfg.is_active,
        "last_run": cfg.last_polled_at,
        "last_error": cfg.last_error,
        "minutes_since_last_run": minutes,
        "cron_interval_minutes": 5,
    })


def _minutes_ago(dt) -> int | None:
    if not dt:
        return None
    delta = timezone.now() - dt
    return int(delta.total_seconds() // 60)


# ─────────────────────────────────────────────────────────────
# Sources mail
# ─────────────────────────────────────────────────────────────

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def mail_sources(request):
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)

    tenant = actor_m.tenant

    if request.method == "GET":
        sources = MailSource.objects.filter(tenant=tenant)
        return Response([_source_payload(s) for s in sources])

    if not _is_tenant_admin(actor_m):
        return Response({"detail": "Not allowed."}, status=status.HTTP_403_FORBIDDEN)

    data = request.data or {}
    email_or_domain = (data.get("email_or_domain") or "").strip().lower()
    if not email_or_domain:
        return Response({"detail": "email_or_domain est requis."}, status=400)

    if MailSource.objects.filter(tenant=tenant, email_or_domain=email_or_domain).exists():
        return Response({"detail": "Cette source existe déjà pour ce tenant."}, status=400)

    src = MailSource.objects.create(
        tenant=tenant,
        client_name=(data.get("client_name") or "").strip(),
        email_or_domain=email_or_domain,
        notes=(data.get("notes") or "").strip(),
        is_active=bool(data.get("is_active", True)),
    )
    return Response(_source_payload(src), status=201)


@api_view(["PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def mail_source_detail(request, source_id: int):
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)
    if not _is_tenant_admin(actor_m):
        return Response({"detail": "Not allowed."}, status=status.HTTP_403_FORBIDDEN)

    try:
        src = MailSource.objects.get(id=source_id, tenant=actor_m.tenant)
    except MailSource.DoesNotExist:
        return Response({"detail": "Source introuvable."}, status=404)

    if request.method == "DELETE":
        src.delete()
        return Response(status=204)

    data = request.data or {}
    if "client_name" in data:
        src.client_name = (data["client_name"] or "").strip()
    if "email_or_domain" in data:
        new_val = (data["email_or_domain"] or "").strip().lower()
        if new_val and new_val != src.email_or_domain:
            if MailSource.objects.filter(tenant=actor_m.tenant, email_or_domain=new_val).exists():
                return Response({"detail": "Cette source existe déjà."}, status=400)
            src.email_or_domain = new_val
    if "notes" in data:
        src.notes = (data["notes"] or "").strip()
    if "is_active" in data:
        src.is_active = bool(data["is_active"])
    src.save()
    return Response(_source_payload(src))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def mail_source_toggle(request, source_id: int):
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)
    if not _is_tenant_admin(actor_m):
        return Response({"detail": "Not allowed."}, status=status.HTTP_403_FORBIDDEN)

    try:
        src = MailSource.objects.get(id=source_id, tenant=actor_m.tenant)
    except MailSource.DoesNotExist:
        return Response({"detail": "Source introuvable."}, status=404)

    src.is_active = not src.is_active
    src.save()
    return Response(_source_payload(src))


# ─────────────────────────────────────────────────────────────
# Stats légères (badge sidebar)
# ─────────────────────────────────────────────────────────────

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def mail_inbox_stats(request):
    """Retourne uniquement les compteurs — endpoint léger pour le badge sidebar."""
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=403)

    base_qs = IncomingMail.objects.filter(tenant=actor_m.tenant)
    if not _is_tenant_admin(actor_m):
        base_qs = base_qs.filter(assigned_to=request.user)

    return Response({
        "pending":    base_qs.filter(status="pending").count(),
        "dispatched": base_qs.filter(status="dispatched").count(),
        "processed":  base_qs.filter(status="processed").count(),
        "rejected":   base_qs.filter(status="rejected").count(),
    })


# ─────────────────────────────────────────────────────────────
# Inbox — mails interceptés
# ─────────────────────────────────────────────────────────────

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def mail_inbox(request):
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)

    is_admin = _is_tenant_admin(actor_m)

    base_qs = IncomingMail.objects.filter(tenant=actor_m.tenant)

    if is_admin:
        # Admins voient tous les mails du tenant
        qs = base_qs.select_related("mail_source", "assigned_to", "dispatched_by").prefetch_related("attachments")
    else:
        # Non-admins : uniquement les mails qui leur sont assignés
        qs = (
            base_qs.filter(assigned_to=request.user)
            .select_related("mail_source", "assigned_to", "dispatched_by")
            .prefetch_related("attachments")
        )

    status_filter = request.GET.get("status")
    if status_filter:
        qs = qs.filter(status=status_filter)

    search = (request.GET.get("search") or "").strip()
    if search:
        qs = qs.filter(
            subject__icontains=search
        ) | qs.filter(from_email__icontains=search) | qs.filter(from_name__icontains=search)

    country = request.GET.get("country")
    if country:
        qs = qs.filter(assigned_country=country)

    # Pagination simple
    page = max(1, int(request.GET.get("page", 1)))
    page_size = 20
    total = qs.count()
    mails = qs.order_by("-received_at")[(page - 1) * page_size: page * page_size]

    if is_admin:
        stats = {
            "total":      base_qs.count(),
            "pending":    base_qs.filter(status="pending").count(),
            "dispatched": base_qs.filter(status="dispatched").count(),
            "processed":  base_qs.filter(status="processed").count(),
            "rejected":   base_qs.filter(status="rejected").count(),
        }
    else:
        my_qs = base_qs.filter(assigned_to=request.user)
        stats = {
            "total":      my_qs.count(),
            "pending":    my_qs.filter(status="pending").count(),
            "dispatched": my_qs.filter(status="dispatched").count(),
            "processed":  my_qs.filter(status="processed").count(),
            "rejected":   my_qs.filter(status="rejected").count(),
        }

    return Response({
        "count": total,
        "page": page,
        "page_size": page_size,
        "is_admin": is_admin,
        "results": [_mail_list_payload(m) for m in mails],
        "stats": stats,
    })


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def mail_inbox_detail(request, mail_id: int):
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)

    try:
        mail = (
            IncomingMail.objects
            .select_related("mail_source", "assigned_to", "dispatched_by", "case")
            .prefetch_related("attachments")
            .get(id=mail_id, tenant=actor_m.tenant)
        )
    except IncomingMail.DoesNotExist:
        return Response({"detail": "Mail introuvable."}, status=404)

    return Response(_mail_detail_payload(mail))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def mail_dispatch(request, mail_id: int):
    """Dispatcher un mail à un responsable pays."""
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)
    if not _is_tenant_admin(actor_m):
        return Response({"detail": "Not allowed."}, status=status.HTTP_403_FORBIDDEN)

    try:
        mail = IncomingMail.objects.select_related("mail_source", "assigned_to").get(
            id=mail_id, tenant=actor_m.tenant
        )
    except IncomingMail.DoesNotExist:
        return Response({"detail": "Mail introuvable."}, status=404)

    data = request.data or {}
    assigned_to_id = data.get("assigned_to_id")
    assigned_country = (data.get("assigned_country") or "").strip().upper()[:2]
    dispatch_note = (data.get("dispatch_note") or "").strip()

    if not assigned_to_id:
        return Response({"detail": "assigned_to_id est requis."}, status=400)

    # Vérifier que l'assigné est bien un responsable pays du tenant
    try:
        assignee_m = Membership.objects.select_related("user").prefetch_related("roles").get(
            tenant=actor_m.tenant, user_id=assigned_to_id, status=Membership.Status.ACTIVE
        )
    except Membership.DoesNotExist:
        return Response({"detail": "Responsable introuvable dans ce tenant."}, status=404)

    mail.assigned_to = assignee_m.user
    mail.assigned_country = assigned_country
    mail.dispatch_note = dispatch_note
    mail.dispatched_by = request.user
    mail.dispatched_at = timezone.now()
    mail.status = IncomingMail.Status.DISPATCHED
    mail.save()

    # Notifier en arrière-plan pour ne pas bloquer la réponse HTTP
    threading.Thread(
        target=_notify_dispatch, args=(mail, assignee_m.user), daemon=True
    ).start()

    return Response(_mail_detail_payload(mail))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def mail_reject(request, mail_id: int):
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)
    if not _is_tenant_admin(actor_m):
        return Response({"detail": "Not allowed."}, status=status.HTTP_403_FORBIDDEN)

    try:
        mail = IncomingMail.objects.get(id=mail_id, tenant=actor_m.tenant)
    except IncomingMail.DoesNotExist:
        return Response({"detail": "Mail introuvable."}, status=404)

    mail.status = IncomingMail.Status.REJECTED
    mail.save(update_fields=["status", "updated_at"])
    return Response(_mail_list_payload(mail))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def mail_restore(request, mail_id: int):
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)
    if not _is_tenant_admin(actor_m):
        return Response({"detail": "Not allowed."}, status=status.HTTP_403_FORBIDDEN)

    try:
        mail = IncomingMail.objects.get(id=mail_id, tenant=actor_m.tenant)
    except IncomingMail.DoesNotExist:
        return Response({"detail": "Mail introuvable."}, status=404)

    mail.status = IncomingMail.Status.PENDING
    mail.assigned_to = None
    mail.assigned_country = ""
    mail.dispatched_at = None
    mail.dispatched_by = None
    mail.dispatch_note = ""
    mail.save()
    return Response(_mail_list_payload(mail))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def mail_mark_processed(request, mail_id: int):
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)

    try:
        mail = IncomingMail.objects.get(id=mail_id, tenant=actor_m.tenant)
    except IncomingMail.DoesNotExist:
        return Response({"detail": "Mail introuvable."}, status=404)

    # Seul l'assigné ou un admin peut marquer comme traité
    is_admin = _is_tenant_admin(actor_m)
    is_assignee = mail.assigned_to_id == request.user.id
    if not is_admin and not is_assignee:
        return Response({"detail": "Not allowed."}, status=403)

    mail.status = IncomingMail.Status.PROCESSED
    mail.save(update_fields=["status", "updated_at"])
    return Response(_mail_list_payload(mail))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def mail_self_dispatch(request, mail_id: int):
    """Admin se dispatche le mail à lui-même."""
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=403)
    if not _is_tenant_admin(actor_m):
        return Response({"detail": "Réservé aux admins."}, status=403)

    try:
        mail = IncomingMail.objects.select_related("mail_source").get(id=mail_id, tenant=actor_m.tenant)
    except IncomingMail.DoesNotExist:
        return Response({"detail": "Mail introuvable."}, status=404)

    dispatch_note = (request.data.get("dispatch_note") or "").strip()

    mail.assigned_to = request.user
    mail.assigned_country = ""
    mail.dispatched_by = request.user
    mail.dispatched_at = timezone.now()
    mail.dispatch_note = dispatch_note
    mail.accepted_at = None
    mail.accepted_by = None
    mail.status = IncomingMail.Status.DISPATCHED
    mail.save()

    return Response(_mail_detail_payload(mail))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def mail_accept(request, mail_id: int):
    """Le responsable désigné accepte formellement le dossier."""
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=403)

    try:
        mail = IncomingMail.objects.select_related(
            "mail_source", "assigned_to", "dispatched_by", "accepted_by"
        ).prefetch_related("attachments").get(id=mail_id, tenant=actor_m.tenant)
    except IncomingMail.DoesNotExist:
        return Response({"detail": "Mail introuvable."}, status=404)

    if mail.status != IncomingMail.Status.DISPATCHED:
        return Response({"detail": "Ce mail n'est pas en statut dispatché."}, status=400)

    is_admin = _is_tenant_admin(actor_m)
    is_assignee = mail.assigned_to_id == request.user.id
    if not is_admin and not is_assignee:
        return Response({"detail": "Vous n'êtes pas l'assigné de ce mail."}, status=403)

    if not mail.accepted_at:
        mail.accepted_at = timezone.now()
        mail.accepted_by = request.user
        mail.save(update_fields=["accepted_at", "accepted_by", "updated_at"])

    return Response(_mail_detail_payload(mail))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def mail_reassign(request, mail_id: int):
    """Le responsable désigné réaffecte le dossier à un collègue."""
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=403)

    try:
        mail = IncomingMail.objects.select_related("mail_source", "assigned_to").get(
            id=mail_id, tenant=actor_m.tenant
        )
    except IncomingMail.DoesNotExist:
        return Response({"detail": "Mail introuvable."}, status=404)

    is_admin = _is_tenant_admin(actor_m)
    is_assignee = mail.assigned_to_id == request.user.id
    if not is_admin and not is_assignee:
        return Response({"detail": "Vous n'êtes pas autorisé à réaffecter ce mail."}, status=403)

    data = request.data or {}
    new_assignee_id = data.get("assigned_to_id")
    dispatch_note = (data.get("dispatch_note") or "").strip()

    if not new_assignee_id:
        return Response({"detail": "assigned_to_id est requis."}, status=400)

    try:
        new_m = Membership.objects.select_related("user").get(
            tenant=actor_m.tenant, user_id=new_assignee_id, status=Membership.Status.ACTIVE
        )
    except Membership.DoesNotExist:
        return Response({"detail": "Membre introuvable dans ce tenant."}, status=404)

    mail.assigned_to = new_m.user
    mail.dispatched_by = request.user
    mail.dispatched_at = timezone.now()
    mail.dispatch_note = dispatch_note
    mail.accepted_at = None
    mail.accepted_by = None
    mail.status = IncomingMail.Status.DISPATCHED
    mail.save()

    threading.Thread(
        target=_notify_dispatch, args=(mail, new_m.user), daemon=True
    ).start()

    return Response(_mail_detail_payload(mail))


# ─────────────────────────────────────────────────────────────
# Responsables Pays du tenant
# ─────────────────────────────────────────────────────────────

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def mail_country_managers(request):
    actor_m = _get_active_membership(request.user)
    if not actor_m or not actor_m.tenant:
        return Response({"detail": "No active tenant."}, status=status.HTTP_403_FORBIDDEN)

    memberships = (
        Membership.objects.filter(
            tenant=actor_m.tenant,
            status=Membership.Status.ACTIVE,
            roles__id=Role.COUNTRY_MANAGER,
        )
        .select_related("user")
        .distinct()
    )

    result = [
        {
            "id": m.user.id,
            "username": m.user.username,
            "full_name": str(m.user),
            "email": m.user.email,
        }
        for m in memberships
    ]
    return Response(result)


# ─────────────────────────────────────────────────────────────
# Notification interne
# ─────────────────────────────────────────────────────────────

def _notify_dispatch(mail: IncomingMail, assignee):
    from django.core.mail import EmailMultiAlternatives
    from django.conf import settings as dj_settings

    if not assignee.email:
        logger.warning("_notify_dispatch: assignee %s has no email, skipping.", assignee.username)
        return

    first_name = assignee.first_name or assignee.username
    subject_line = f"[ACX] Demande client assignée — {mail.subject[:80]}"
    country_label = mail.assigned_country or "—"
    body_excerpt = (mail.body_text or "").strip()[:300]
    if len(mail.body_text or "") > 300:
        body_excerpt += "…"
    note_block = f"\n    Note de dispatch : {mail.dispatch_note}\n" if mail.dispatch_note else ""

    frontend_url = getattr(dj_settings, "FRONTEND_BASE_URL", "https://acx-acremac.net")
    inbox_url = f"{frontend_url}/fr/mail-inbox"

    # ── Texte brut (fallback) ────────────────────────────────
    text_body = (
        f"Bonjour {first_name},\n\n"
        f"Un dossier client vous a été assigné sur la plateforme ACX.\n\n"
        f"De      : {mail.from_name or mail.from_email} <{mail.from_email}>\n"
        f"Objet   : {mail.subject}\n"
        f"Pays    : {country_label}\n"
        f"Reçu le : {mail.received_at.strftime('%d/%m/%Y à %H:%M')}\n"
        f"{note_block}\n"
        f"Aperçu :\n{body_excerpt}\n\n"
        f"Connectez-vous pour consulter et prendre en charge ce dossier :\n{inbox_url}\n\n"
        f"Cordialement,\nL'équipe ACX — ACREMAC"
    )

    # ── HTML ─────────────────────────────────────────────────
    note_html = (
        f'<tr><td style="padding:10px 0 0"><div style="background:#fffbeb;border-left:3px solid '
        f'#f59e0b;border-radius:4px;padding:10px 14px;font-size:13px;color:#92400e;">'
        f'<strong>Note :</strong> {mail.dispatch_note}</div></td></tr>'
    ) if mail.dispatch_note else ""

    excerpt_html = (
        f'<tr><td style="padding:14px 0 0"><p style="margin:0 0 6px;font-size:11px;'
        f'font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#94a3b8;">Aperçu</p>'
        f'<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;padding:12px 14px;'
        f'font-size:13px;color:#475569;line-height:1.6;white-space:pre-wrap;">{body_excerpt}</div>'
        f'</td></tr>'
    ) if body_excerpt else ""

    html_body = f"""<!DOCTYPE html>
<html lang="fr">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>
<body style="margin:0;padding:0;background:#f1f5f9;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background:#f1f5f9;padding:32px 16px;">
    <tr><td align="center">
      <table width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;">

        <!-- Logo / header -->
        <tr><td style="background:linear-gradient(135deg,#1e1b4b 0%,#312e81 100%);border-radius:12px 12px 0 0;padding:28px 32px;">
          <table width="100%" cellpadding="0" cellspacing="0"><tr>
            <td><span style="font-size:22px;font-weight:800;color:#fff;letter-spacing:-.5px;">ACX</span>
              <span style="font-size:13px;color:#a5b4fc;margin-left:8px;">ACREMAC</span></td>
            <td align="right"><span style="background:rgba(255,255,255,.15);color:#c7d2fe;
              font-size:11px;font-weight:600;padding:4px 10px;border-radius:20px;">Nouvelle demande</span></td>
          </tr></table>
        </td></tr>

        <!-- Body -->
        <tr><td style="background:#fff;padding:32px;border-left:1px solid #e2e8f0;border-right:1px solid #e2e8f0;">
          <p style="margin:0 0 20px;font-size:16px;color:#1e293b;">
            Bonjour <strong>{first_name}</strong>,
          </p>
          <p style="margin:0 0 24px;font-size:14px;color:#475569;line-height:1.6;">
            Un dossier client vous a été assigné sur la plateforme <strong>ACX</strong>.
            Veuillez en prendre connaissance et y donner suite dans les meilleurs délais.
          </p>

          <!-- Fiche mail -->
          <table width="100%" cellpadding="0" cellspacing="0"
            style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:0;">
            <tr><td style="padding:16px 20px;">
              <table width="100%" cellpadding="0" cellspacing="0">
                <tr>
                  <td style="padding:4px 0;font-size:12px;color:#94a3b8;width:90px;">Expéditeur</td>
                  <td style="padding:4px 0;font-size:13px;color:#1e293b;font-weight:600;">
                    {mail.from_name or mail.from_email}
                    {('<br><span style="font-weight:400;color:#64748b;font-size:12px;">' + mail.from_email + '</span>') if mail.from_name else ''}
                  </td>
                </tr>
                <tr>
                  <td style="padding:4px 0;font-size:12px;color:#94a3b8;">Objet</td>
                  <td style="padding:4px 0;font-size:13px;color:#1e293b;">{mail.subject or '(Sans objet)'}</td>
                </tr>
                <tr>
                  <td style="padding:4px 0;font-size:12px;color:#94a3b8;">Pays</td>
                  <td style="padding:4px 0;font-size:13px;color:#1e293b;">{country_label}</td>
                </tr>
                <tr>
                  <td style="padding:4px 0;font-size:12px;color:#94a3b8;">Reçu le</td>
                  <td style="padding:4px 0;font-size:13px;color:#1e293b;">
                    {mail.received_at.strftime('%d/%m/%Y à %H:%M')}
                  </td>
                </tr>
              </table>
            </td></tr>
          </table>

          <table width="100%" cellpadding="0" cellspacing="0">
            {note_html}
            {excerpt_html}
          </table>

          <!-- CTA -->
          <table width="100%" cellpadding="0" cellspacing="0" style="margin-top:28px;">
            <tr><td align="center">
              <a href="{inbox_url}" target="_blank"
                style="display:inline-block;background:linear-gradient(135deg,#1e1b4b 0%,#4f46e5 100%);
                color:#fff;text-decoration:none;font-size:14px;font-weight:700;
                padding:14px 32px;border-radius:8px;letter-spacing:.02em;">
                Ouvrir le dossier →
              </a>
            </td></tr>
          </table>
        </td></tr>

        <!-- Footer -->
        <tr><td style="background:#f8fafc;border:1px solid #e2e8f0;border-top:none;
          border-radius:0 0 12px 12px;padding:20px 32px;text-align:center;">
          <p style="margin:0;font-size:11px;color:#94a3b8;line-height:1.6;">
            Cet email a été envoyé automatiquement par la plateforme ACX — ACREMAC.<br>
            Ne pas répondre directement à cet email.
          </p>
        </td></tr>

      </table>
    </td></tr>
  </table>
</body>
</html>"""

    logger.info(
        "_notify_dispatch: sending to %s via %s:%s",
        assignee.email, dj_settings.EMAIL_HOST, dj_settings.EMAIL_PORT,
    )
    try:
        msg = EmailMultiAlternatives(
            subject=subject_line,
            body=text_body,
            from_email=dj_settings.DEFAULT_FROM_EMAIL,
            to=[assignee.email],
        )
        msg.attach_alternative(html_body, "text/html")
        msg.send(fail_silently=False)
        logger.info("_notify_dispatch: email sent successfully to %s", assignee.email)
    except Exception as exc:
        logger.error("_notify_dispatch: failed to send email to %s — %s", assignee.email, exc)


# ─── SSE — flux temps réel (badge + inbox) ────────────────────────────────────

@condition(etag_func=None)
def mail_sse(request):
    """
    Server-Sent Events : pousse un événement JSON dès que le compteur 'pending'
    change pour ce tenant/utilisateur. L'authentification se fait via le token
    JWT passé en query param (?token=...) car EventSource ne supporte pas
    les headers personnalisés.
    """
    from rest_framework_simplejwt.tokens import UntypedToken
    from rest_framework_simplejwt.exceptions import TokenError
    from django.http import HttpResponse

    token_str = request.GET.get("token", "")
    try:
        validated = UntypedToken(token_str)
        user_id = validated.payload.get("user_id")
        user = get_user_model().objects.get(id=user_id, is_active=True)
    except (TokenError, Exception):
        return HttpResponse("Unauthorized", status=401)

    membership = Membership.objects.filter(
        user=user, status=Membership.Status.ACTIVE
    ).select_related("tenant").first()
    if not membership or not membership.tenant:
        return HttpResponse("Forbidden", status=403)

    tenant = membership.tenant
    is_admin = _is_tenant_admin(membership)

    def event_stream():
        last_pending = -1
        tick_count = 0
        while True:
            try:
                base_qs = IncomingMail.objects.filter(tenant=tenant)
                if not is_admin:
                    base_qs = base_qs.filter(assigned_to=user)
                pending = base_qs.filter(status="pending").count()

                if pending != last_pending:
                    last_pending = pending
                    payload = json.dumps({"type": "update", "pending": pending})
                    yield f"data: {payload}\n\n"

                # Keep-alive toutes les 20s pour éviter le timeout Nginx
                tick_count += 1
                if tick_count % 2 == 0:
                    yield ": ping\n\n"

                time.sleep(10)
            except GeneratorExit:
                break
            except Exception:
                break

    response = StreamingHttpResponse(event_stream(), content_type="text/event-stream")
    response["Cache-Control"] = "no-cache"
    response["X-Accel-Buffering"] = "no"   # désactive le buffer Nginx
    response["Connection"] = "keep-alive"
    return response


# ─── Téléchargement sécurisé des pièces jointes ───────────────────────────────

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def mail_attachment_download(request, attachment_id):
    """
    Sert la pièce jointe uniquement si l'utilisateur appartient au même tenant
    que le mail auquel elle est rattachée.
    """
    actor_m = Membership.objects.filter(
        user=request.user, status=Membership.Status.ACTIVE
    ).select_related("tenant").first()
    if not actor_m:
        return Response({"detail": "Accès refusé."}, status=403)

    try:
        att = MailAttachment.objects.select_related("mail__tenant").get(id=attachment_id)
    except MailAttachment.DoesNotExist:
        raise Http404

    if att.mail.tenant_id != actor_m.tenant_id:
        return Response({"detail": "Accès refusé."}, status=403)

    if not att.file or not att.file.name:
        return Response({"detail": "Fichier introuvable."}, status=404)

    try:
        file_handle = att.file.open("rb")
    except (FileNotFoundError, OSError):
        return Response({"detail": "Fichier introuvable sur le serveur."}, status=404)

    content_type, _ = mimetypes.guess_type(att.filename or att.file.name)
    content_type = content_type or "application/octet-stream"

    response = FileResponse(file_handle, content_type=content_type)
    filename = (att.filename or os.path.basename(att.file.name)).replace('"', "")
    response["Content-Disposition"] = f'attachment; filename="{filename}"'
    return response
