from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Request
from pydantic import BaseModel, EmailStr, Field

from app.config import cloudflare_token_path, settings
from app.deps import bind_onboarding_session, get_session_from_request
from app.services import activity_log, carbonio, dns_verify, domain_registry, notifications, onboard_handoff
from app.services import infrastructure, ops_webhook, support_tickets
from app.services.cloudflare import CloudflareDNS, CloudflareError, is_cloudflare_sandbox, mail_dns_records, wizard_nameservers
from app.services.domain_format import cloudflare_error_http_detail, invalid_domain_http_detail, normalize_domain, validate_primary_domain
from app.services.mail_aliases import sanitize_mail_aliases

router = APIRouter(
    prefix="/onboarding",
    tags=["onboarding"],
    dependencies=[Depends(bind_onboarding_session)],
)


def _cloudflare_token_missing_error() -> str:
    return (
        "Token Cloudflare não configurado. Na VM112: "
        f"{cloudflare_token_path()} (ver secrets/README.txt)"
    )


class DomainRequest(BaseModel):
    domain: str = Field(..., min_length=3, max_length=253)


class ValidateDomainRequest(BaseModel):
    domain: str = Field(..., min_length=3, max_length=253)
    mail_aliases: list[str] = Field(default_factory=list, max_length=5)


class CreateAccountRequest(BaseModel):
    domain: str
    local_part: str = "admin"
    password: str | None = Field(None, min_length=8)
    use_server_password: bool = False
    display_name: str | None = None
    notify_email: EmailStr | None = None
    send_welcome: bool = True
    dns_mode: str | None = None
    mail_aliases: list[str] = Field(default_factory=list, max_length=5)


class CloudflareApplyRequest(BaseModel):
    domain: str
    zone_id: str | None = None
    use_cloudflare: bool = True
    mail_aliases: list[str] = Field(default_factory=list, max_length=5)


class SupportTicketRequest(BaseModel):
    domain: str = Field(..., min_length=3, max_length=253)
    account_email: str | None = None
    client_note: str | None = Field(default=None, max_length=2000)


def _client_settings(domain: str, email: str) -> dict:
    mail = f"mail.{domain}"
    return {
        "imap": {"host": mail, "port": 993, "security": "SSL/TLS"},
        "smtp": {"host": mail, "port": 465, "security": "SSL/TLS"},
        "pop3": {"host": mail, "port": 995, "security": "SSL/TLS"},
        "webmail": f"https://{mail}/",
        "email": email,
        "smtp_note": "Use a porta 465 com SSL/TLS para envio. Não use a porta 587.",
    }


@router.get("/health")
def health():
    return {"status": "ok", "service": "ibytera-mail-portal"}


@router.post("/session/reset")
def reset_session(
    request: Request,
    session_id: str = Depends(bind_onboarding_session),
    domain: str | None = None,
):
    """Limpa logs da sessão actual e, opcionalmente, histórico unificado do domínio."""
    get_session_from_request(request)
    cleared_sessions: list[str] = []
    if session_id:
        activity_log.clear(session_id)
        cleared_sessions.append(session_id)
    domain_q = (domain or "").strip().lower()
    if domain_q and len(domain_q) >= 3:
        cleared_sessions.extend(activity_log.clear_for_domain(domain_q))
    cleared_sessions = sorted(set(cleared_sessions))
    activity_log.info("Sessão reiniciada — cache de logs limpo", source="portal")
    return {
        "ok": True,
        "cleared_sessions": cleared_sessions,
        "message": "Cache da sessão limpo. Recarregue a página.",
    }


@router.post("/support/ticket")
def open_support_ticket(
    body: SupportTicketRequest,
    background_tasks: BackgroundTasks,
    session_id: str = Depends(bind_onboarding_session),
):
    """Abre chamado de suporte (webmail/infra pendente) com link da sessão completa."""
    domain = body.domain.lower().strip()
    aliases = domain_registry.get_domain_record(domain)
    mail_aliases = (aliases or {}).get("mail_aliases") or []
    infra_status = infrastructure.get_status(domain, mail_aliases)

    layman = support_tickets.build_layman_explanation(domain, body.account_email, infra_status)
    ticket, created = support_tickets.find_or_create_ticket(
        domain=domain,
        session_id=session_id or "",
        account_email=body.account_email,
        infra_status=infra_status,
        client_note=body.client_note,
    )

    pending = [s.get("label", s.get("id")) for s in infra_status.get("steps", []) if not s.get("ok")]
    try:
        notifications.notify_support_ticket_opened(
            ticket_id=ticket["ticket_id"],
            domain=domain,
            account_email=body.account_email,
            support_url=ticket["support_url"],
            pending_summary="; ".join(pending) or "A verificar",
            session_id=session_id or "",
            client_note=body.client_note,
        )
    except Exception as exc:
        activity_log.warn(f"Notificação de chamado falhou (ticket criado): {exc}", source="portal")

    if created:
        background_tasks.add_task(
            ops_webhook.emit_event,
            "onboarding.escalated",
            domain=domain,
            session_id=session_id or "",
            data={
                "reason": "client_support_request",
                "wizard_ticket_id": ticket["ticket_id"],
                "client_note": body.client_note,
                "source": "wizard_support_modal",
            },
        )

    if created:
        activity_log.info(
            f"Cliente abriu chamado {ticket['ticket_id']} — suporte notificado",
            source="portal",
        )
    else:
        activity_log.info(
            f"Cliente reutilizou chamado {ticket['ticket_id']} — sem duplicar",
            source="portal",
        )

    msg = (
        "Chamado registrado. Nossa equipe (assistente + suporte humano) recebeu o histórico desta sessão."
        if created
        else f"Já existe um chamado aberto ({ticket['ticket_id']}). Acompanhe o andamento abaixo."
    )

    return {
        "ticket_id": ticket["ticket_id"],
        "support_url": ticket["support_url"],
        "tracking_url": ticket.get("tracking_url") or ticket["support_url"],
        "created": created,
        "layman": layman,
        "message": msg,
    }


@router.get("/support/ticket/by-session")
def get_open_ticket_for_session(session_id: str = Depends(bind_onboarding_session)):
    """Chamado OB- aberto para banner no wizard."""
    ticket = support_tickets.find_open_ticket_for_session(session_id or "")
    if not ticket:
        return {"ticket": None}
    return {
        "ticket": {
            "ticket_id": ticket.get("ticket_id"),
            "tracking_url": ticket.get("tracking_url") or ticket.get("support_url"),
            "status": ticket.get("status"),
        }
    }


@router.get("/support/ticket/{ticket_id}")
def get_support_ticket(ticket_id: str):
    ticket = support_tickets.get_ticket(ticket_id)
    if not ticket:
        raise HTTPException(404, "Chamado não encontrado.")
    return ticket


class SupportTicketNoteRequest(BaseModel):
    session_id: str = Field(..., min_length=8)
    message: str = Field(..., min_length=1, max_length=2000)


@router.get("/support/ticket/{ticket_id}/public")
def get_support_ticket_public(
    ticket_id: str,
    session_id: str = Query(..., min_length=8),
):
    """Portal cliente Spec 026 — requer par ticket + session."""
    from app.services import assist_session, infrastructure
    from app.services.domain_registry import get_domain_record

    ticket = support_tickets.get_ticket(ticket_id.strip().upper())
    if not ticket:
        raise HTTPException(404, detail={"message": "Chamado não encontrado."})
    if (ticket.get("session_id") or "").strip() != session_id.strip():
        raise HTTPException(403, detail={"message": "Sessão não corresponde a este chamado."})

    domain = ticket.get("domain") or ""
    rec = get_domain_record(domain) if domain else None
    aliases = (rec or {}).get("mail_aliases") or []
    infra_status = infrastructure.get_status(domain, aliases) if domain else {}
    ticket["infra_status"] = infra_status
    assist_state = assist_session.public_state(session_id.strip())
    return support_tickets.build_public_view(ticket, infra_status=infra_status, assist_state=assist_state)


@router.post("/support/ticket/{ticket_id}/note")
def add_support_ticket_note(
    ticket_id: str,
    body: SupportTicketNoteRequest,
    session_id: str = Depends(bind_onboarding_session),
):
    sid = (body.session_id or session_id or "").strip()
    updated = support_tickets.add_client_note(ticket_id.strip().upper(), sid, body.message)
    if not updated:
        raise HTTPException(404, detail={"message": "Chamado não encontrado ou sessão inválida."})
    from app.services import assist_session, infrastructure
    from app.services.domain_registry import get_domain_record

    domain = updated.get("domain") or ""
    rec = get_domain_record(domain) if domain else None
    aliases = (rec or {}).get("mail_aliases") or []
    infra_status = infrastructure.get_status(domain, aliases) if domain else {}
    assist_state = assist_session.public_state(sid)
    return support_tickets.build_public_view(updated, infra_status=infra_status, assist_state=assist_state)


@router.get("/activity-log")
def get_activity_log(
    session_id: str = Depends(bind_onboarding_session),
    domain: str | None = None,
):
    domain_q = (domain or "").strip().lower()
    if domain_q and len(domain_q) >= 3:
        entries, sessions_merged = activity_log.get_entries_for_domain(domain_q)
        return {
            "session_id": session_id or None,
            "domain": domain_q,
            "sessions_merged": sessions_merged,
            "entries": entries,
        }
    if not session_id:
        return {"entries": [], "session_id": None}
    return {
        "session_id": session_id,
        "entries": activity_log.get_entries(session_id),
        "sessions_merged": [session_id],
    }


@router.get("/infrastructure/status/{domain}")
def infrastructure_status(domain: str):
    return infrastructure.get_status(domain.lower().strip())


@router.post("/infrastructure/provision")
def infrastructure_provision(
    body: DomainRequest,
    request: Request,
    step: str | None = Query(None),
):
    get_session_from_request(request)
    domain = body.domain.lower().strip()
    activity_log.info(f"Provisionar infra: {domain}", source="traefik")
    try:
        return infrastructure.provision(domain, step_id=step)
    except Exception as e:
        activity_log.error(str(e), source="traefik")
        raise HTTPException(400, str(e)) from e


@router.post("/validate-domain")
def validate_domain(body: ValidateDomainRequest, request: Request):
    get_session_from_request(request)
    domain = normalize_domain(body.domain)
    try:
        validate_primary_domain(domain)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=invalid_domain_http_detail()) from e
    aliases = sanitize_mail_aliases(body.mail_aliases, domain)
    if body.mail_aliases:
        domain_registry.save_mail_aliases(domain, aliases)
        activity_log.info(
            f"Alias registados: {', '.join(aliases) if aliases else 'nenhum'}",
            source="portal",
        )
    activity_log.info(f"Validar domínio: {domain}", source="portal")
    exists = carbonio.domain_exists(domain)
    if exists:
        activity_log.ok(f"Carbonio: domínio {domain} já existe", source="vm112")
    else:
        activity_log.info(
            f"Carbonio: domínio {domain} ainda não existe (normal — será criado ao «Criar conta agora»)",
            source="vm112",
        )
    cf_auto = False
    if settings.cloudflare_api_token:
        if is_cloudflare_sandbox():
            activity_log.info(
                "Cloudflare sandbox: token de teste activo (sem chamadas à API real)",
                source="cloudflare",
            )
            cf_auto = True
        else:
            try:
                cf = CloudflareDNS()
                activity_log.info("Verificar zona Cloudflare…", source="cloudflare")
                cf_auto = cf.get_zone_by_name(domain) is not None
                activity_log.ok(
                    f"Cloudflare: zona {'encontrada' if cf_auto else 'ainda não na conta Ligbox'}",
                    source="cloudflare",
                )
            except CloudflareError as e:
                activity_log.warn(f"Cloudflare: {e}", source="cloudflare")
    else:
        activity_log.warn("Token Cloudflare não configurado", source="cloudflare")

    return {
        "domain": domain,
        "mail_aliases": aliases,
        "exists_in_carbonio": exists,
        "carbonio_will_create_domain": not exists,
        "carbonio_provisioning": True,
        "cloudflare_auto_dns": cf_auto,
        "message": (
            f"O domínio {'já existe' if exists else 'será criado'} no Carbonio ao criar a conta. "
            f"{'Cloudflare: DNS automático OK.' if cf_auto else 'Cloudflare: configure DNS no passo seguinte.'}"
        ),
    }


def _cloudflare_zone_status(domain: str) -> dict:
    domain = domain.lower().strip()
    if not settings.cloudflare_api_token:
        return {
            "domain": domain,
            "cloudflare_configured": False,
            "zone_in_account": False,
            "can_apply_mail_records": False,
            "zone_status": None,
            "nameservers": [],
            "managed_zones": [],
            "portal_onboarding_required": True,
            "message": "API Cloudflare não configurada no servidor.",
        }
    cf = CloudflareDNS()
    cf.verify_token()
    managed = cf.list_zone_names()
    zone = cf.get_zone_by_name(domain)
    in_account = zone is not None
    zone_status = zone.get("status") if zone else None
    nameservers = wizard_nameservers(zone) if zone else []

    return {
        "domain": domain,
        "cloudflare_configured": True,
        "zone_in_account": in_account,
        "can_apply_mail_records": in_account,
        "zone_id": zone["id"] if zone else None,
        "zone_status": zone_status,
        "nameservers": nameservers,
        "managed_zones": managed,
        "portal_onboarding_required": not in_account,
        "message": (
            f"Zona {domain} pronta na Cloudflare Ligbox ({zone_status})."
            if in_account
            else f"Adicione {domain} na Cloudflare Ligbox e altere os NS no registrador."
        ),
    }


@router.get("/dns/cloudflare/status/{domain}")
def cloudflare_zone_status(domain: str):
    try:
        activity_log.info(f"Estado Cloudflare: {domain}", source="cloudflare")
        return _cloudflare_zone_status(domain.lower().strip())
    except CloudflareError as e:
        activity_log.error(str(e), source="cloudflare")
        raise HTTPException(502, f"Erro Cloudflare: {e}") from e


def _portal_onboarding_payload(domain: str, zone_created: bool = False) -> dict:
    status = _cloudflare_zone_status(domain)
    nameservers = status.get("nameservers") or []
    in_account = status.get("zone_in_account")

    if in_account:
        return {
            "domain": domain,
            "ready": True,
            "zone_created": zone_created,
            "steps": [
                {
                    "order": 1,
                    "title": "Domínio na Cloudflare Ligbox",
                    "done": True,
                    "detail": (
                        f"Zona criada automaticamente. Estado: {status.get('zone_status', 'pending')}."
                        if zone_created
                        else f"Zona já existia ({status.get('zone_status', 'active')})."
                    ),
                },
                {
                    "order": 2,
                    "title": "Alterar nameservers no registrador",
                    "done": status.get("zone_status") == "active",
                    "detail": "Aponte o domínio para os nameservers Cloudflare listados abaixo.",
                },
                {
                    "order": 3,
                    "title": "Registos de email",
                    "done": False,
                    "detail": "Use «Criar apontamentos de email» para MX, A, SPF e DMARC.",
                },
            ],
            "nameservers": nameservers,
            "status": status,
        }

    return {
        "domain": domain,
        "ready": False,
        "zone_created": False,
        "steps": [
            {
                "order": 1,
                "title": "Criar zona na Cloudflare",
                "done": False,
                "detail": "Não foi possível criar a zona. Verifique o API token.",
            },
        ],
        "nameservers": [],
        "status": status,
    }


@router.post("/dns/cloudflare/provision-zone")
def provision_cloudflare_zone(body: DomainRequest, request: Request):
    get_session_from_request(request)
    domain = normalize_domain(body.domain)
    try:
        validate_primary_domain(domain)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=invalid_domain_http_detail()) from e
    if not settings.cloudflare_api_token:
        raise HTTPException(400, _cloudflare_token_missing_error())

    cf = CloudflareDNS()
    try:
        activity_log.info(f"Criar zona Cloudflare: {domain}", source="cloudflare")
        cf.verify_token()
        result = cf.ensure_zone(domain)
        zone = result["zone"]
        created = result["created"]
        sandbox = is_cloudflare_sandbox()
        if sandbox:
            activity_log.info(
                "Cloudflare sandbox: zona simulada (token de teste — não cria zona real)",
                source="cloudflare",
            )
        activity_log.ok(
            f"Zona {domain} {'criada' if created else 'já existia'} (id {zone.get('id', '?')})",
            source="cloudflare",
        )
        payload = _portal_onboarding_payload(domain, zone_created=created)
        payload["zone_id"] = zone.get("id")
        payload["sandbox"] = sandbox
        payload["message"] = (
            f"Domínio {domain} {'criado' if created else 'já existia'} na Cloudflare Ligbox. "
            + (
                "Ambiente de teste: apontamentos simulados automaticamente."
                if sandbox
                else "Altere os nameservers no registrador."
            )
        )
        if nameservers := wizard_nameservers(zone):
            payload["nameservers"] = nameservers
            payload["status"]["nameservers"] = nameservers
        if sandbox:
            zone_id = zone.get("id")
            applied = []
            for rec in mail_dns_records(domain, settings.mail_public_ip, []):
                upsert = cf.upsert_record(
                    zone_id,
                    rec["type"],
                    rec["name"],
                    rec["content"],
                    priority=rec.get("priority"),
                    proxied=rec.get("proxied", False),
                    zone_name=domain,
                )
                applied.append({"type": rec["type"], "name": rec["name"], "id": upsert.get("id")})
            verification = dns_verify.verify_mail_dns(domain, settings.mail_public_ip)
            payload["applied"] = applied
            payload["verification"] = verification
            for step in payload.get("steps") or []:
                if step.get("order") == 3:
                    step["done"] = True
                    step["detail"] = "Apontamentos simulados (sandbox)."
            activity_log.ok("Cloudflare sandbox: apontamentos de email simulados", source="cloudflare")
        return payload
    except CloudflareError as e:
        activity_log.error(f"Provision zona falhou: {e}", source="cloudflare")
        raise HTTPException(
            status_code=502,
            detail=cloudflare_error_http_detail(e, domain),
        ) from e


@router.get("/dns/portal-onboarding/{domain}")
def portal_dns_onboarding(domain: str):
    domain = domain.lower().strip()
    try:
        return _portal_onboarding_payload(domain, zone_created=False)
    except CloudflareError as e:
        activity_log.error(str(e), source="cloudflare")
        raise HTTPException(502, f"Erro Cloudflare: {e}") from e


@router.get("/dns/instructions/{domain}")
def dns_instructions(domain: str):
    domain = domain.lower().strip()
    mail_host = f"mail.{domain}"
    ip = settings.mail_public_ip
    activity_log.info(f"Instruções DNS para {domain}", source="portal")
    cf_status = None
    try:
        cf_status = _cloudflare_zone_status(domain)
    except CloudflareError:
        pass
    return {
        "domain": domain,
        "mail_public_ip": ip,
        "cloudflare": cf_status,
        "dns_paths": {
            "portal": "Trazer DNS para o portal (Cloudflare conta Ligbox)",
            "external": "Manter DNS no provedor atual (apontamentos manuais)",
        },
        "records": [
            {"type": "A", "name": mail_host, "value": ip, "note": "DNS only (sem proxy Cloudflare)"},
            {"type": "MX", "name": domain, "value": mail_host, "priority": 10},
            {"type": "TXT", "name": domain, "value": f"v=spf1 mx a:{mail_host} ip4:{ip} -all"},
            {
                "type": "TXT",
                "name": f"_dmarc.{domain}",
                "value": f"v=DMARC1; p=quarantine; rua=mailto:postmaster@{domain}",
            },
            {
                "type": "TXT",
                "name": f"default._domainkey.{domain}",
                "value": "(DKIM — Carbonio Admin após criar domínio)",
            },
        ],
        "cloudflare_hint": "Escolha no passo DNS: trazer para o portal ou manter no provedor atual.",
    }


@router.get("/dns/verify/{domain}")
def verify_dns(domain: str):
    domain = domain.lower().strip()
    activity_log.info(f"Verificar DNS público: {domain}", source="portal")
    result = dns_verify.verify_mail_dns(domain, settings.mail_public_ip)
    if result.get("ready"):
        activity_log.ok("DNS público: MX e A mail OK", source="portal")
    else:
        activity_log.warn("DNS público: ainda incompleto (MX ou A mail)", source="portal")
    return result


@router.post("/dns/cloudflare/apply")
def apply_cloudflare_dns(body: CloudflareApplyRequest, request: Request):
    get_session_from_request(request)
    domain = body.domain.lower().strip()
    if not settings.cloudflare_api_token:
        raise HTTPException(400, _cloudflare_token_missing_error())

    cf = CloudflareDNS()
    try:
        activity_log.info(f"Aplicar registos mail na Cloudflare: {domain}", source="cloudflare")
        cf.verify_token()
    except CloudflareError as e:
        activity_log.error(str(e), source="cloudflare")
        raise HTTPException(401, f"Token Cloudflare inválido: {e}") from e

    zone_id = body.zone_id
    if not zone_id:
        zone = cf.get_zone_by_name(domain)
        if not zone:
            activity_log.error(f"Zona {domain} não está na conta Ligbox", source="cloudflare")
            raise HTTPException(
                status_code=422,
                detail={
                    "code": "zone_not_in_account",
                    "domain": domain,
                    "message": f"Zona {domain} ainda não está na Cloudflare Ligbox.",
                    "use_portal_onboarding": True,
                },
            )
        zone_id = zone["id"]

    rec = domain_registry.get_domain_record(domain)
    aliases = sanitize_mail_aliases(body.mail_aliases, domain) if body.mail_aliases else []
    if not aliases and rec:
        aliases = list(rec.get("mail_aliases") or [])

    applied = []
    for rec in mail_dns_records(domain, settings.mail_public_ip, aliases):
        result = cf.upsert_record(
            zone_id,
            rec["type"],
            rec["name"],
            rec["content"],
            priority=rec.get("priority"),
            proxied=rec.get("proxied", False),
            zone_name=domain,
        )
        applied.append({"type": rec["type"], "name": rec["name"], "id": result.get("id")})
        activity_log.ok(f"DNS {rec['type']} {rec['name']} → {rec['content']}", source="cloudflare")

    verification = dns_verify.verify_mail_dns(domain, settings.mail_public_ip)
    return {"domain": domain, "zone_id": zone_id, "applied": applied, "verification": verification}


@router.get("/session/password-status")
def session_password_status(request: Request):
    """Spec 016b — indica se a senha do cadastro está no vault servidor (sem expor valor)."""
    sid = get_session_from_request(request)
    if not sid:
        return {"ready": False, "source": None}
    ready = onboard_handoff.session_has_password(sid)
    return {"ready": ready, "source": "portal_handoff" if ready else None}


@router.delete("/session/password")
def clear_session_password(request: Request):
    """Remove senha do vault (ex.: utilizador escolhe senha diferente no wizard)."""
    sid = get_session_from_request(request)
    if sid:
        onboard_handoff.clear_session_password(sid)
    return {"ok": True}


@router.post("/account/create")
def create_account(body: CreateAccountRequest, request: Request):
    sid = get_session_from_request(request)
    domain = body.domain.lower().strip()
    email = f"{body.local_part}@{domain}"
    webmail = f"https://mail.{domain}/"
    dns_mode = body.dns_mode or "não indicado"
    aliases = sanitize_mail_aliases(body.mail_aliases, domain)
    if aliases:
        domain_registry.save_mail_aliases(domain, aliases)

    if body.use_server_password:
        password = onboard_handoff.pop_password_for_session(sid) if sid else None
        if not password:
            raise HTTPException(
                400,
                "Senha do cadastro expirou ou não está disponível. Defina a senha manualmente no passo Conta admin.",
            )
    else:
        password = (body.password or "").strip()
        if len(password) < 8:
            raise HTTPException(400, "A senha precisa de pelo menos 8 caracteres.")
        if sid:
            onboard_handoff.clear_session_password(sid)

    activity_log.info(f"Iniciar criação de conta: {email}", source="portal")

    try:
        out, account_reused = carbonio.ensure_onboarding_account(
            email, password, body.display_name
        )
        if account_reused:
            activity_log.info(
                f"Conta reutilizada (retry wizard): {email} — senha actualizada",
                source="vm112",
            )
        else:
            activity_log.ok(f"zmprov ca concluído: {out or 'OK'}", source="vm112")
    except carbonio.CarbonioError as e:
        activity_log.error(f"Criação falhou: {e}", source="vm112")
        raise HTTPException(400, str(e)) from e

    verified = carbonio.account_exists(email)
    if verified:
        activity_log.ok(f"Verificação: conta {email} existe no Carbonio", source="vm112")
        domain_registry.register_portal_admin(domain, email, mail_aliases=aliases)
        activity_log.ok(f"Painel admin: {email} autorizado para {domain}", source="portal")
        if aliases:
            carbonio.add_mail_alias_hostnames(domain, aliases)
            activity_log.ok(f"Alias Carbonio: {len(aliases)} hostname(s)", source="vm112")
    else:
        activity_log.error(
            f"ALERTA: zmprov ca executou mas conta {email} NÃO encontrada (ga)",
            source="vm112",
        )

    verification = None
    try:
        verification = dns_verify.verify_mail_dns(domain, settings.mail_public_ip)
    except Exception as e:
        activity_log.warn(f"Verificação DNS: {e}", source="portal")

    notifications_sent = {
        "admin": notifications.notify_admin_onboarding(
            domain,
            email,
            dns_mode,
            verified,
            verification.get("ready") if verification else None,
        ),
        "client": False,
        "welcome": False,
    }

    if body.notify_email:
        notifications_sent["client"] = notifications.notify_client_contact(
            str(body.notify_email),
            domain,
            email,
            webmail,
            verified,
        )

    if body.send_welcome and verified:
        notifications_sent["welcome"] = notifications.send_welcome_to_mailbox(
            email,
            domain,
            webmail,
            body.display_name,
        )

    activity_log.ok("Processo de onboarding finalizado", source="portal")

    infra_status = None
    try:
        infra_status = infrastructure.provision(domain, mail_aliases=aliases)
        activity_log.ok("Provisionamento infra (Traefik/cert/SNI) concluído", source="traefik")
    except Exception as e:
        activity_log.warn(f"Infra automática: {e} — use a coluna Infraestrutura", source="traefik")

    return {
        "email": email,
        "message": out,
        "account_reused": account_reused,
        "webmail": webmail,
        "account_verified": verified,
        "notifications_sent": notifications_sent,
        "client_settings": _client_settings(domain, email),
        "dns_verification": verification,
        "needs_review": not verified,
        "mail_aliases": aliases,
        "infrastructure": infra_status.get("status") if infra_status else infrastructure.get_status(domain, aliases),
    }
