from datetime import date, timedelta
from decimal import Decimal

from django.conf import settings
from django.contrib import admin
from django.db.models import Count, Q, Sum
from django.urls import reverse
from django.shortcuts import render
from django.utils import timezone


MONTHS_PT = ("Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez")


def environment_callback(request):
    if getattr(settings, "DEMO_MODE", False):
        return ["Demonstração", "warning"]
    return ["Desenvolvimento", "warning"] if settings.DEBUG else ["Produção", "success"]


def _start_of_month(value: date, offset: int = 0) -> date:
    year = value.year
    month = value.month + offset
    while month < 1:
        month += 12
        year -= 1
    while month > 12:
        month -= 12
        year += 1
    return date(year, month, 1)


def _chart_points(values, width=620, height=190, padding=18):
    numeric = [float(value or 0) for value in values]
    maximum = max(numeric + [1.0])
    available_width = width - 2 * padding
    available_height = height - 2 * padding
    step = available_width / max(len(numeric) - 1, 1)
    points = []
    for index, value in enumerate(numeric):
        x = padding + step * index
        y = height - padding - (value / maximum) * available_height
        points.append((round(x, 2), round(y, 2)))
    return " ".join(f"{x},{y}" for x, y in points), points, maximum


def _percent(value, total):
    if not total:
        return 0
    return min(round((float(value) / float(total)) * 100), 100)


def _pending_payment_queryset():
    from billing.models import Payment

    return Payment.objects.select_related("renewal__service__client").exclude(
        status__in=[Payment.Status.PAID, Payment.Status.CANCELLED, Payment.Status.REFUNDED]
    )


def task_badge(request):
    from operations.models import InternalTask

    return InternalTask.objects.exclude(status__in=[InternalTask.Status.DONE, InternalTask.Status.CANCELLED]).count()


def renewal_badge(request):
    from billing.models import Renewal

    today = timezone.localdate()
    return Renewal.objects.exclude(status__in=[Renewal.Status.RENEWED, Renewal.Status.CANCELLED]).filter(
        due_date__lte=today + timedelta(days=30)
    ).count()


def payment_badge(request):
    return _pending_payment_queryset().count()


def notification_badge(request):
    from notifications.models import NotificationLog
    from monitoring.models import MonitorCheck, MonitorIncident, WebsiteMonitor
    from monitoring.models import MonitorCheck, MonitorIncident, WebsiteMonitor

    return NotificationLog.objects.filter(
        status__in=[
            NotificationLog.Status.DRAFT,
            NotificationLog.Status.SCHEDULED,
            NotificationLog.Status.FAILED,
            NotificationLog.Status.SUPPRESSED,
        ]
    ).count()


def support_badge(request):
    from support.models import SupportTicket

    return SupportTicket.objects.exclude(status__in=[SupportTicket.Status.RESOLVED, SupportTicket.Status.CLOSED]).count()


def monitoring_badge(request):
    from monitoring.models import MonitorIncident, WebsiteMonitor

    down = WebsiteMonitor.objects.filter(status=WebsiteMonitor.Status.DOWN).count()
    incidents = MonitorIncident.objects.filter(
        status__in=[MonitorIncident.Status.OPEN, MonitorIncident.Status.ACKNOWLEDGED]
    ).count()
    return max(down, incidents)


def import_badge(request):
    from imports.models import ImportBatch, ImportRow

    batches = ImportBatch.objects.filter(status__in=[ImportBatch.Status.REVIEW, ImportBatch.Status.FAILED]).count()
    pending_errors = ImportRow.objects.filter(
        decision=ImportRow.Decision.PENDING,
        severity__in=[ImportRow.Severity.WARNING, ImportRow.Severity.ERROR],
    ).count()
    return batches + pending_errors


def dashboard_callback(request, context):
    from billing.models import Payment, Renewal
    from clients.models import Client
    from core.choices import ServiceStatus, ServiceType
    from core.models import Document
    from domains.models import Domain
    from imports.models import ImportRow
    from notifications.models import NotificationLog
    from monitoring.models import MonitorCheck, MonitorIncident, WebsiteMonitor
    from operations.models import InternalTask
    from services.models import Service
    from support.models import SupportTicket
    from technical.models import (BackupRecord, SSLCertificate, TechnicalAsset, WordPressPlugin, WordPressSite)

    today = timezone.localdate()
    now = timezone.now()
    next_7 = today + timedelta(days=7)
    next_30 = today + timedelta(days=30)
    next_60 = today + timedelta(days=60)

    active_services = Service.objects.filter(status=ServiceStatus.ACTIVE)
    upcoming = (
        active_services.select_related("client")
        .filter(next_renewal_date__gte=today, next_renewal_date__lte=next_30)
        .order_by("next_renewal_date")[:9]
    )
    overdue_services = Service.objects.filter(next_renewal_date__lt=today).exclude(
        status__in=[ServiceStatus.CANCELLED, ServiceStatus.TRANSFERRED, ServiceStatus.ARCHIVED]
    )
    pending_payments_qs = _pending_payment_queryset()
    pending_tasks_qs = InternalTask.objects.select_related("client", "service", "assigned_to").exclude(
        status__in=[InternalTask.Status.DONE, InternalTask.Status.CANCELLED]
    ).order_by("due_date", "-priority")
    overdue_tasks_count = sum(1 for task in pending_tasks_qs if task.is_overdue)
    open_tickets_qs = SupportTicket.objects.select_related("client", "service", "assigned_to").exclude(
        status__in=[SupportTicket.Status.RESOLVED, SupportTicket.Status.CLOSED]
    ).order_by("-priority", "-last_activity_at")
    recent_notifications = NotificationLog.objects.select_related("client", "service").order_by("-created_at")[:6]

    expected_month = Renewal.objects.filter(
        due_date__year=today.year,
        due_date__month=today.month,
    ).aggregate(total=Sum("sale_amount"))["total"] or Decimal("0.00")
    received_month = Payment.objects.filter(
        status=Payment.Status.PAID,
        paid_at__year=today.year,
        paid_at__month=today.month,
    ).aggregate(total=Sum("amount"))["total"] or Decimal("0.00")
    outstanding_total = pending_payments_qs.aggregate(total=Sum("amount"))["total"] or Decimal("0.00")

    monthly_finance = []
    for offset in range(-5, 1):
        month = _start_of_month(today, offset)
        expected = Renewal.objects.filter(due_date__year=month.year, due_date__month=month.month).aggregate(
            total=Sum("sale_amount")
        )["total"] or Decimal("0.00")
        received = Payment.objects.filter(
            status=Payment.Status.PAID,
            paid_at__year=month.year,
            paid_at__month=month.month,
        ).aggregate(total=Sum("amount"))["total"] or Decimal("0.00")
        monthly_finance.append(
            {
                "label": MONTHS_PT[month.month - 1],
                "year": month.year,
                "expected": expected,
                "received": received,
            }
        )

    expected_points, expected_coordinates, chart_max = _chart_points([item["expected"] for item in monthly_finance])
    received_points, received_coordinates, received_max = _chart_points([item["received"] for item in monthly_finance])
    chart_max = max(chart_max, received_max)
    # Recalculate both series against a shared scale.
    expected_points, expected_coordinates, _ = _chart_points_shared([item["expected"] for item in monthly_finance], chart_max)
    received_points, received_coordinates, _ = _chart_points_shared([item["received"] for item in monthly_finance], chart_max)

    service_counts = {
        row["service_type"]: row["count"]
        for row in active_services.values("service_type").annotate(count=Count("id"))
    }
    distribution_palette = ["#2684ff", "#16b7db", "#6c5ce7", "#ff7a67", "#2fbd8a", "#f6b84b", "#7d8ca3"]
    distribution = []
    active_total = sum(service_counts.values())
    start = 0
    gradient_parts = []
    for index, (value, label) in enumerate(ServiceType.choices):
        count = service_counts.get(value, 0)
        if not count:
            continue
        percentage = round((count / active_total) * 100, 1) if active_total else 0
        color = distribution_palette[index % len(distribution_palette)]
        end = start + percentage
        gradient_parts.append(f"{color} {start:.1f}% {end:.1f}%")
        distribution.append({"label": label, "count": count, "percentage": percentage, "color": color})
        start = end
    distribution_gradient = f"conic-gradient({', '.join(gradient_parts)})" if gradient_parts else "#e6edf7"

    renewal_buckets = [
        {
            "label": "Vencidos",
            "count": overdue_services.count(),
            "tone": "danger",
        },
        {
            "label": "Próximos 7 dias",
            "count": active_services.filter(next_renewal_date__range=(today, next_7)).count(),
            "tone": "warning",
        },
        {
            "label": "8 a 30 dias",
            "count": active_services.filter(next_renewal_date__gt=next_7, next_renewal_date__lte=next_30).count(),
            "tone": "primary",
        },
        {
            "label": "31 a 60 dias",
            "count": active_services.filter(next_renewal_date__gt=next_30, next_renewal_date__lte=next_60).count(),
            "tone": "info",
        },
    ]
    renewal_total = sum(item["count"] for item in renewal_buckets)
    for item in renewal_buckets:
        item["percentage"] = _percent(item["count"], renewal_total)

    top_clients = list(
        Renewal.objects.filter(due_date__year=today.year)
        .values("service__client__name")
        .annotate(total=Sum("sale_amount"), services=Count("service", distinct=True))
        .order_by("-total")[:5]
    )
    top_client_max = max([float(row["total"] or 0) for row in top_clients] + [1])
    for row in top_clients:
        row["percentage"] = round((float(row["total"] or 0) / top_client_max) * 100)

    data_quality = {
        "services_without_date": Service.objects.filter(is_recurring=True, next_renewal_date__isnull=True).count(),
        "services_without_value": Service.objects.filter(is_recurring=True, sale_price=0, is_complimentary=False).count(),
        "clients_without_email": Client.objects.filter(email="").count(),
        "domains_without_registrar": Domain.objects.filter(registrar__isnull=True).count(),
        "documents_without_file": Document.objects.filter(file="").count(),
        "import_rows_pending": ImportRow.objects.filter(decision=ImportRow.Decision.PENDING).count(),
    }
    data_quality_total = sum(data_quality.values())

    infrastructure = {
        "assets_total": TechnicalAsset.objects.count(),
        "assets_attention": TechnicalAsset.objects.filter(status=TechnicalAsset.Status.ATTENTION).count(),
        "ssl_missing": TechnicalAsset.objects.filter(asset_type=TechnicalAsset.AssetType.WEBSITE, ssl_active=False).count(),
        "failed_backups": BackupRecord.objects.filter(status=BackupRecord.Status.FAILED).count(),
        "wordpress_attention": WordPressSite.objects.filter(health_status__in=[WordPressSite.HealthStatus.ATTENTION, WordPressSite.HealthStatus.CRITICAL]).count(),
        "plugins_vulnerable": WordPressPlugin.objects.filter(security_status=WordPressPlugin.SecurityStatus.VULNERABLE).count(),
        "plugins_updates": sum(1 for plugin in WordPressPlugin.objects.exclude(available_version="") if plugin.update_required),
        "ssl_expiring": SSLCertificate.objects.filter(status=SSLCertificate.Status.EXPIRING).count(),
        "monitors_total": WebsiteMonitor.objects.count(),
        "monitors_down": WebsiteMonitor.objects.filter(status=WebsiteMonitor.Status.DOWN).count(),
        "monitors_degraded": WebsiteMonitor.objects.filter(status=WebsiteMonitor.Status.DEGRADED).count(),
        "monitor_incidents": MonitorIncident.objects.filter(status__in=[MonitorIncident.Status.OPEN, MonitorIncident.Status.ACKNOWLEDGED]).count(),
    }
    monitor_checks_24h = MonitorCheck.objects.filter(checked_at__gte=now - timedelta(hours=24)).exclude(result=MonitorCheck.Result.SKIPPED)
    monitor_checks_ok_24h = monitor_checks_24h.filter(result__in=[MonitorCheck.Result.UP, MonitorCheck.Result.DEGRADED]).count()
    infrastructure["monitor_uptime_24h"] = round((monitor_checks_ok_24h / monitor_checks_24h.count()) * 100, 2) if monitor_checks_24h.exists() else None

    cards = [
        {
            "title": "Renovações em 30 dias",
            "value": active_services.filter(next_renewal_date__range=(today, next_30)).count(),
            "icon": "event_upcoming",
            "url": reverse("admin:billing_renewal_changelist"),
            "tone": "blue",
            "caption": "Serviços a acompanhar",
        },
        {
            "title": "Pagamentos pendentes",
            "value": pending_payments_qs.count(),
            "icon": "payments",
            "url": reverse("admin:billing_payment_changelist"),
            "tone": "coral",
            "caption": f"{outstanding_total:.2f} € em aberto",
        },
        {
            "title": "Pedidos de suporte",
            "value": open_tickets_qs.count(),
            "icon": "support_agent",
            "url": reverse("admin:support_supportticket_changelist"),
            "tone": "violet",
            "caption": "Comunicação com clientes",
        },
        {
            "title": "Recebido este mês",
            "value": f"{received_month:.0f}",
            "icon": "trending_up",
            "url": reverse("admin:billing_payment_changelist"),
            "tone": "green",
            "caption": "€ confirmados",
            "suffix": " €",
        },
        {
            "title": "Tarefas atrasadas",
            "value": overdue_tasks_count,
            "icon": "assignment_late",
            "url": reverse("admin:operations_internaltask_changelist"),
            "tone": "red",
            "caption": "Requerem intervenção",
        },
        {
            "title": "Serviços ativos",
            "value": active_services.count(),
            "icon": "cloud_done",
            "url": reverse("admin:services_service_changelist"),
            "tone": "cyan",
            "caption": "Todos os tipos",
        },
        {
            "title": "Infraestrutura",
            "value": infrastructure["assets_attention"] + infrastructure["ssl_missing"] + infrastructure["failed_backups"] + infrastructure["wordpress_attention"] + infrastructure["plugins_vulnerable"] + infrastructure["ssl_expiring"] + infrastructure["monitors_down"] + infrastructure["monitors_degraded"],
            "icon": "monitor_heart",
            "url": reverse("monitoring_center"),
            "tone": "navy",
            "caption": "Alertas técnicos",
        },
        {
            "title": "Qualidade dos dados",
            "value": data_quality_total,
            "icon": "data_check",
            "url": reverse("admin:imports_importrow_changelist"),
            "tone": "amber",
            "caption": "Campos e importações a rever",
        },
    ]

    context.update(
        {
            "demo_mode": getattr(settings, "DEMO_MODE", False),
            "today": today,
            "dashboard_cards": cards,
            "upcoming_services": upcoming,
            "pending_payments": pending_payments_qs.order_by("requested_at")[:6],
            "pending_tasks": pending_tasks_qs[:6],
            "open_tickets": open_tickets_qs[:6],
            "recent_notifications": recent_notifications,
            "expected_month": expected_month,
            "received_month": received_month,
            "financial_progress": _percent(received_month, expected_month),
            "outstanding_total": outstanding_total,
            "data_quality": data_quality,
            "data_quality_total": data_quality_total,
            "infrastructure": infrastructure,
            "monthly_finance": monthly_finance,
            "expected_points": expected_points,
            "expected_coordinates": expected_coordinates,
            "received_points": received_points,
            "received_coordinates": received_coordinates,
            "chart_max": chart_max,
            "service_distribution": distribution,
            "service_distribution_gradient": distribution_gradient,
            "service_distribution_total": active_total,
            "renewal_buckets": renewal_buckets,
            "top_clients": top_clients,
            "year": today.year,
            "system_time": now,
        }
    )
    return context


def _chart_points_shared(values, maximum, width=620, height=190, padding=18):
    numeric = [float(value or 0) for value in values]
    available_width = width - 2 * padding
    available_height = height - 2 * padding
    step = available_width / max(len(numeric) - 1, 1)
    points = []
    maximum = max(float(maximum or 0), 1.0)
    for index, value in enumerate(numeric):
        x = padding + step * index
        y = height - padding - (value / maximum) * available_height
        points.append((round(x, 2), round(y, 2)))
    return " ".join(f"{x},{y}" for x, y in points), points, maximum

# ---------------------------------------------------------------------------
# V4 — centros operacionais, qualidade e relatórios
# ---------------------------------------------------------------------------


def _admin_filter_url(name, params=""):
    url = reverse(name)
    return f"{url}?{params}" if params else url


def quality_metrics():
    from clients.models import Client
    from core.models import Document
    from domains.models import Domain
    from hosting.models import HostingAccount
    from monitoring.models import MonitorIncident, WebsiteMonitor
    from services.models import Service
    from technical.models import (BackupRecord, SSLCertificate, TechnicalAsset, WordPressPlugin, WordPressSite)

    metrics = [
        {
            "key": "clients_without_email",
            "label": "Clientes sem email",
            "description": "Impedem comunicações e notificações automáticas.",
            "count": Client.objects.filter(email="").count(),
            "icon": "mail_off",
            "tone": "danger",
            "url": _admin_filter_url("admin:clients_client_changelist", "email__exact="),
        },
        {
            "key": "clients_without_phone",
            "label": "Clientes sem telefone",
            "description": "Contactos incompletos para MB WAY ou suporte.",
            "count": Client.objects.filter(phone="").count(),
            "icon": "phone_disabled",
            "tone": "warning",
            "url": _admin_filter_url("admin:clients_client_changelist", "phone__exact="),
        },
        {
            "key": "services_without_date",
            "label": "Serviços recorrentes sem data",
            "description": "Não podem gerar renovações nem alertas.",
            "count": Service.objects.filter(is_recurring=True, next_renewal_date__isnull=True).count(),
            "icon": "event_busy",
            "tone": "danger",
            "url": _admin_filter_url("admin:services_service_changelist", "is_recurring__exact=1&next_renewal_date__isnull=True"),
        },
        {
            "key": "services_without_value",
            "label": "Serviços sem valor",
            "description": "Serviços recorrentes sem preço de venda e não marcados como oferta.",
            "count": Service.objects.filter(is_recurring=True, sale_price=0, is_complimentary=False).count(),
            "icon": "money_off",
            "tone": "warning",
            "url": _admin_filter_url("admin:services_service_changelist", "sale_price__exact=0"),
        },
        {
            "key": "domains_without_registrar",
            "label": "Domínios sem registador",
            "description": "Falta identificar o fornecedor responsável pela renovação.",
            "count": Domain.objects.filter(registrar__isnull=True).count(),
            "icon": "language",
            "tone": "warning",
            "url": _admin_filter_url("admin:domains_domain_changelist", "registrar__isnull=True"),
        },
        {
            "key": "hosting_without_server",
            "label": "Alojamentos sem servidor",
            "description": "A infraestrutura de destino ainda não está identificada.",
            "count": HostingAccount.objects.filter(server__isnull=True).count(),
            "icon": "dns",
            "tone": "warning",
            "url": _admin_filter_url("admin:hosting_hostingaccount_changelist", "server__isnull=True"),
        },
        {
            "key": "technical_without_path",
            "label": "Ativos sem pasta",
            "description": "Websites sem raiz documental registada.",
            "count": TechnicalAsset.objects.filter(asset_type=TechnicalAsset.AssetType.WEBSITE, document_root="").count(),
            "icon": "folder_off",
            "tone": "warning",
            "url": _admin_filter_url("admin:technical_technicalasset_changelist", "document_root__exact="),
        },
        {
            "key": "failed_backups",
            "label": "Backups com falha",
            "description": "Necessitam de verificação e nova execução.",
            "count": BackupRecord.objects.filter(status=BackupRecord.Status.FAILED).count(),
            "icon": "backup",
            "tone": "danger",
            "url": _admin_filter_url("admin:technical_backuprecord_changelist", f"status__exact={BackupRecord.Status.FAILED}"),
        },
        {
            "key": "wordpress_attention",
            "label": "WordPress com alertas",
            "description": "Instalações que requerem atualização, revisão ou intervenção técnica.",
            "count": WordPressSite.objects.filter(health_status__in=[WordPressSite.HealthStatus.ATTENTION, WordPressSite.HealthStatus.CRITICAL]).count(),
            "icon": "web",
            "tone": "warning",
            "url": _admin_filter_url("admin:technical_wordpresssite_changelist", "health_status__in=attention,critical"),
        },
        {
            "key": "vulnerable_plugins",
            "label": "Plugins vulneráveis",
            "description": "Plugins marcados como vulneráveis e que exigem intervenção urgente.",
            "count": WordPressPlugin.objects.filter(security_status=WordPressPlugin.SecurityStatus.VULNERABLE).count(),
            "icon": "bug_report",
            "tone": "danger",
            "url": _admin_filter_url("admin:technical_wordpressplugin_changelist", f"security_status__exact={WordPressPlugin.SecurityStatus.VULNERABLE}"),
        },
        {
            "key": "ssl_expiring",
            "label": "Certificados SSL a expirar",
            "description": "Certificados assinalados para renovação próxima.",
            "count": SSLCertificate.objects.filter(status=SSLCertificate.Status.EXPIRING).count(),
            "icon": "lock_clock",
            "tone": "warning",
            "url": _admin_filter_url("admin:technical_sslcertificate_changelist", f"status__exact={SSLCertificate.Status.EXPIRING}"),
        },
        {
            "key": "documents_without_file",
            "label": "Documentos sem ficheiro",
            "description": "Registos documentais que ainda não têm anexo.",
            "count": Document.objects.filter(file="").count(),
            "icon": "draft",
            "tone": "info",
            "url": _admin_filter_url("admin:core_document_changelist", "file__exact="),
        },
        {
            "key": "monitors_never_checked",
            "label": "Monitores nunca verificados",
            "description": "Monitores ativos sem qualquer execução registada.",
            "count": WebsiteMonitor.objects.filter(is_active=True, last_checked_at__isnull=True).count(),
            "icon": "monitor_heart",
            "tone": "warning",
            "url": _admin_filter_url("admin:monitoring_websitemonitor_changelist", "is_active__exact=1&last_checked_at__isnull=True"),
        },
        {
            "key": "monitor_incidents_open",
            "label": "Incidentes de monitorização ativos",
            "description": "Falhas de disponibilidade ainda abertas ou reconhecidas.",
            "count": MonitorIncident.objects.filter(status__in=[MonitorIncident.Status.OPEN, MonitorIncident.Status.ACKNOWLEDGED]).count(),
            "icon": "crisis_alert",
            "tone": "danger",
            "url": _admin_filter_url("admin:monitoring_monitorincident_changelist", "status__in=open,acknowledged"),
        },
    ]
    return metrics


def quality_center(request):
    from django.shortcuts import render
    from imports.models import ImportBatch, ImportRow

    metrics = quality_metrics()
    context = {
        **admin.site.each_context(request),
        "title": "Centro de qualidade dos dados",
        "metrics": metrics,
        "total_issues": sum(item["count"] for item in metrics),
        "import_batches_review": ImportBatch.objects.filter(status__in=(ImportBatch.Status.REVIEW, ImportBatch.Status.READY)).count(),
        "import_rows_pending": ImportRow.objects.filter(decision=ImportRow.Decision.PENDING).count(),
        "recent_batches": ImportBatch.objects.order_by("-created_at")[:5],
    }
    return render(request, "admin/quality_center.html", context)


def operations_center(request):
    from django.shortcuts import render
    from billing.models import Payment, Renewal
    from notifications.models import NotificationLog
    from operations.models import InternalTask
    from services.models import Service

    today = timezone.localdate()
    next_7 = today + timedelta(days=7)
    open_task_statuses = (InternalTask.Status.TODO, InternalTask.Status.IN_PROGRESS, InternalTask.Status.WAITING)
    pending_payment_statuses = (Payment.Status.PENDING, Payment.Status.WAITING_PROOF, Payment.Status.VALIDATING, Payment.Status.PARTIAL)
    open_renewal_statuses = (
        Renewal.Status.TO_NOTIFY, Renewal.Status.NOTIFIED, Renewal.Status.WAITING_PAYMENT,
        Renewal.Status.PARTIAL, Renewal.Status.PAID, Renewal.Status.OVERDUE,
    )
    context = {
        **admin.site.each_context(request),
        "title": "Centro de operações",
        "today": today,
        "tasks_today": InternalTask.objects.select_related("client", "service").filter(status__in=open_task_statuses, due_date=today)[:12],
        "renewals_today": Renewal.objects.select_related("service__client").filter(status__in=open_renewal_statuses, due_date=today)[:12],
        "payments_validating": Payment.objects.select_related("renewal__service__client").filter(status=Payment.Status.VALIDATING)[:12],
        "notifications_pending": NotificationLog.objects.select_related("client", "service").filter(
            status__in=(
                NotificationLog.Status.DRAFT,
                NotificationLog.Status.SCHEDULED,
                NotificationLog.Status.FAILED,
                NotificationLog.Status.SUPPRESSED,
            )
        )[:12],
        "tasks_next": InternalTask.objects.select_related("client", "service").filter(status__in=open_task_statuses, due_date__gt=today, due_date__lte=next_7).order_by("due_date")[:15],
        "renewals_next": Renewal.objects.select_related("service__client").filter(status__in=open_renewal_statuses, due_date__gt=today, due_date__lte=next_7).order_by("due_date")[:15],
        "overdue_tasks": InternalTask.objects.select_related("client", "service").filter(status__in=open_task_statuses, due_date__lt=today).order_by("due_date")[:15],
        "overdue_renewals": Renewal.objects.select_related("service__client").filter(status__in=open_renewal_statuses, due_date__lt=today).order_by("due_date")[:15],
        "pending_payments": Payment.objects.select_related("renewal__service__client").filter(status__in=pending_payment_statuses).order_by("requested_at")[:15],
        "services_without_renewal": Service.objects.filter(is_recurring=True, next_renewal_date__isnull=True).select_related("client")[:10],
    }
    context["counts"] = {
        "today": len(context["tasks_today"]) + len(context["renewals_today"]),
        "next_7": len(context["tasks_next"]) + len(context["renewals_next"]),
        "overdue": len(context["overdue_tasks"]) + len(context["overdue_renewals"]),
        "payments": Payment.objects.filter(status__in=pending_payment_statuses).count(),
    }
    return render(request, "admin/operations_center.html", context)


def _report_rows(report_name):
    from billing.models import Payment, Renewal
    from core.choices import ServiceStatus
    from services.models import Service

    today = timezone.localdate()
    if report_name == "renewals":
        headers = ["Cliente", "Serviço", "Tipo", "Vencimento", "Estado", "Valor", "Pago", "Em falta"]
        qs = Renewal.objects.select_related("service__client").order_by("due_date")
        rows = [[r.client.name, r.service.name, r.service.get_service_type_display(), r.due_date, r.get_status_display(), r.sale_amount, r.amount_paid, r.outstanding_amount] for r in qs]
        title = "Relatório de renovações"
    elif report_name == "payments":
        headers = ["Cliente", "Serviço", "Valor", "Método", "Estado", "Solicitado em", "Pago em", "Fatura/recibo"]
        qs = Payment.objects.select_related("renewal__service__client").order_by("-requested_at")
        rows = [[p.client.name, p.renewal.service.name, p.amount, p.get_method_display(), p.get_status_display(), p.requested_at, p.paid_at, p.invoice_number] for p in qs]
        title = "Relatório de pagamentos"
    elif report_name == "services":
        headers = ["Cliente", "Serviço", "Tipo", "Estado", "Próxima renovação", "Custo", "Venda", "Margem"]
        qs = Service.objects.select_related("client", "provider").order_by("client__name", "name")
        rows = [[s.client.name, s.name, s.get_service_type_display(), s.get_status_display(), s.next_renewal_date, s.cost_price, s.sale_price, s.margin] for s in qs]
        title = "Relatório de serviços"
    elif report_name == "overdue":
        headers = ["Cliente", "Serviço", "Data", "Dias em atraso", "Valor", "Estado"]
        qs = Renewal.objects.select_related("service__client").filter(due_date__lt=today).exclude(status__in=(Renewal.Status.RENEWED, Renewal.Status.CANCELLED)).order_by("due_date")
        rows = [[r.client.name, r.service.name, r.due_date, abs(r.days_until_due), r.outstanding_amount, r.get_status_display()] for r in qs]
        title = "Relatório de valores e renovações em atraso"
    elif report_name == "quality":
        headers = ["Indicador", "Descrição", "Ocorrências"]
        rows = [[m["label"], m["description"], m["count"]] for m in quality_metrics()]
        title = "Relatório de qualidade dos dados"
    elif report_name == "monitoring":
        from monitoring.models import WebsiteMonitor
        headers = ["Cliente", "Website", "URL", "Estado", "Uptime 30d", "Último HTTP", "Resposta (ms)", "Última verificação", "Incidente aberto"]
        qs = WebsiteMonitor.objects.select_related("technical_asset__service__client").prefetch_related("incidents").order_by("technical_asset__service__client__name", "name")
        rows = []
        for monitor in qs:
            client = monitor.technical_asset.service.client
            incident = monitor.current_incident
            rows.append([client.name, monitor.name, monitor.url, monitor.get_status_display(), monitor.uptime_percentage(days=30), monitor.last_http_status, monitor.last_response_ms, monitor.last_checked_at, incident.get_status_display() if incident else ""])
        title = "Relatório de monitorização de websites"
    elif report_name == "communications":
        from notifications.models import NotificationLog
        headers = ["Criada em", "Cliente", "Serviço", "Assunto", "Destinatário original", "Modo", "Estado", "Tentativas", "Enviado em", "Erro"]
        qs = NotificationLog.objects.select_related("client", "service").order_by("-created_at")
        rows = [[n.created_at, n.client.name, n.service.name if n.service else "", n.subject, n.original_recipient or n.recipient, n.get_delivery_mode_display() if n.delivery_mode else "", n.get_status_display(), f"{n.attempts_count}/{n.max_attempts}", n.sent_at, n.error_message] for n in qs]
        title = "Relatório de comunicações"
    else:
        raise ValueError("Relatório desconhecido.")
    return title, headers, rows


def report_center(request):
    from django.shortcuts import render

    reports = [
        {"key": "renewals", "title": "Renovações", "description": "Agenda completa, valores recebidos e montantes em falta.", "icon": "event_repeat"},
        {"key": "payments", "title": "Pagamentos", "description": "Movimentos financeiros, métodos e referências.", "icon": "payments"},
        {"key": "services", "title": "Serviços", "description": "Carteira comercial, custos, vendas e margens.", "icon": "inventory_2"},
        {"key": "overdue", "title": "Em atraso", "description": "Renovações vencidas e valores ainda em aberto.", "icon": "running_with_errors"},
        {"key": "quality", "title": "Qualidade dos dados", "description": "Indicadores de campos incompletos e riscos operacionais.", "icon": "data_check"},
        {"key": "communications", "title": "Comunicações", "description": "Envios, modo de entrega, tentativas, falhas e destinatários.", "icon": "campaign"},
        {"key": "monitoring", "title": "Monitorização", "description": "Uptime, tempos de resposta, estados e incidentes dos websites.", "icon": "monitor_heart"},
    ]
    return render(request, "admin/report_center.html", {**admin.site.each_context(request), "title": "Relatórios e exportações", "reports": reports})


def report_export(request, report_name, file_format):
    import csv
    from io import BytesIO
    from django.http import HttpResponse
    from openpyxl import Workbook

    title, headers, rows = _report_rows(report_name)
    filename = f"{report_name}-{timezone.localdate():%Y%m%d}"
    if file_format == "csv":
        response = HttpResponse(content_type="text/csv; charset=utf-8")
        response["Content-Disposition"] = f'attachment; filename="{filename}.csv"'
        response.write("\ufeff")
        writer = csv.writer(response, delimiter=";")
        writer.writerow(headers)
        for row in rows:
            writer.writerow([value.isoformat() if hasattr(value, "isoformat") else value for value in row])
        return response
    if file_format == "xlsx":
        workbook = Workbook()
        sheet = workbook.active
        sheet.title = title[:31]
        sheet.append(headers)
        for row in rows:
            sheet.append([value.replace(tzinfo=None) if hasattr(value, "tzinfo") and value.tzinfo else value for value in row])
        sheet.freeze_panes = "A2"
        sheet.auto_filter.ref = sheet.dimensions
        for column in sheet.columns:
            max_length = min(max(len(str(cell.value or "")) for cell in column) + 2, 45)
            sheet.column_dimensions[column[0].column_letter].width = max_length
        output = BytesIO()
        workbook.save(output)
        response = HttpResponse(output.getvalue(), content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
        response["Content-Disposition"] = f'attachment; filename="{filename}.xlsx"'
        return response
    raise ValueError("Formato não suportado.")


def report_print(request, report_name):
    from django.shortcuts import render

    title, headers, rows = _report_rows(report_name)
    return render(request, "admin/report_print.html", {**admin.site.each_context(request), "title": title, "headers": headers, "rows": rows, "generated_at": timezone.now()})


def communications_center(request):
    from django.contrib import messages
    from django.shortcuts import redirect, render
    from django_celery_beat.models import PeriodicTask

    from core.models import PortalConfiguration
    from notifications.models import NotificationAttempt, NotificationLog, NotificationTemplate
    from notifications.scheduling import ensure_notification_schedule
    from notifications.workflows import (
        prepare_due_notifications,
        retry_failed_notifications,
        send_scheduled_notifications,
        send_test_email,
    )

    config = PortalConfiguration.objects.order_by("pk").first()
    if request.method == "POST":
        action = request.POST.get("action")
        try:
            if action == "prepare":
                result = prepare_due_notifications(send=False, automatic=False)
                messages.success(
                    request,
                    f"Preparação concluída: {result['created']} novas, {result['existing']} existentes e {result['skipped']} ignoradas.",
                )
            elif action == "send_scheduled":
                result = send_scheduled_notifications(user=request.user)
                messages.success(request, f"Fila processada: {result['sent']} enviadas e {result['failed']} falharam.")
            elif action == "retry_failed":
                result = retry_failed_notifications(user=request.user, force=True)
                messages.success(request, f"Repetição concluída: {result['sent']} enviadas e {result['failed']} falharam.")
            elif action == "test_email":
                recipient = (request.POST.get("recipient") or "").strip()
                if not recipient:
                    raise ValueError("Indique um email para o teste.")
                notification = send_test_email(recipient, user=request.user)
                messages.success(request, f"Email de teste processado em modo {notification.get_delivery_mode_display()}.")
            elif action == "setup_schedule":
                tasks = ensure_notification_schedule()
                messages.success(request, f"{len(tasks)} agendamentos de automação configurados.")
            else:
                messages.warning(request, "Ação desconhecida.")
        except Exception as exc:
            messages.error(request, f"Não foi possível concluir a ação: {exc}")
        return redirect("communications_center")

    now = timezone.now()
    last_30 = now - timedelta(days=30)
    notification_tasks = PeriodicTask.objects.filter(name__startswith="Portal —").order_by("name")
    warnings = []
    if not config:
        warnings.append("Ainda não existe configuração global do portal.")
    else:
        if config.email_test_mode and not config.email_test_recipient:
            warnings.append("O modo de teste está ativo, mas não existe destinatário de testes definido.")
        if not config.email_test_mode and "console" in getattr(settings, "EMAIL_BACKEND", ""):
            warnings.append("O modo de teste está desligado, mas o backend atual escreve apenas no terminal.")
        if config.auto_send_notifications and config.email_test_mode and not config.email_test_recipient:
            warnings.append("O envio automático está ativo sem um destinatário de teste seguro.")
        if not config.auto_send_notifications:
            warnings.append("O envio automático está desligado; os avisos serão preparados para revisão manual.")

    context = {
        **admin.site.each_context(request),
        "title": "Centro de comunicações",
        "config": config,
        "warnings": warnings,
        "counts": {
            "draft": NotificationLog.objects.filter(status=NotificationLog.Status.DRAFT).count(),
            "scheduled": NotificationLog.objects.filter(status=NotificationLog.Status.SCHEDULED).count(),
            "failed": NotificationLog.objects.filter(status=NotificationLog.Status.FAILED).count(),
            "sent_30": NotificationLog.objects.filter(status=NotificationLog.Status.SENT, sent_at__gte=last_30).count(),
        },
        "scheduled_notifications": NotificationLog.objects.select_related("client", "service").filter(
            status=NotificationLog.Status.SCHEDULED
        ).order_by("scheduled_for")[:10],
        "failed_notifications": NotificationLog.objects.select_related("client", "service").filter(
            status=NotificationLog.Status.FAILED
        ).order_by("next_retry_at", "-last_attempt_at")[:10],
        "recent_attempts": NotificationAttempt.objects.select_related("notification__client", "triggered_by").order_by("-attempted_at")[:12],
        "inactive_templates": NotificationTemplate.objects.filter(is_active=False).count(),
        "automatic_templates": NotificationTemplate.objects.filter(is_active=True, allow_automatic_send=True).count(),
        "periodic_tasks": notification_tasks,
        "backend": getattr(settings, "EMAIL_BACKEND", ""),
        "broker": getattr(settings, "CELERY_BROKER_URL", ""),
    }
    return render(request, "admin/communications_center.html", context)


def status_legend(request):
    from billing.models import Payment, Renewal
    from clients.models import Client
    from core.choices import ClientStatus, ServiceStatus
    from core.status_palette import (
        ASSET_STATUS_LABELS,
        BACKUP_STATUS_LABELS,
        CLIENT_STATUS_LABELS,
        DOMAIN_STATUS_LABELS,
        HOSTING_STATUS_LABELS,
        NOTIFICATION_STATUS_LABELS,
        PAYMENT_STATUS_LABELS,
        RENEWAL_STATUS_LABELS,
        SERVER_STATUS_LABELS,
        SERVICE_STATUS_LABELS,
        STATUS_MEANINGS,
        TASK_STATUS_LABELS,
        TICKET_STATUS_LABELS,
        WEBSITE_STATUS_LABELS,
        WORDPRESS_HEALTH_LABELS,
        WORDPRESS_PLUGIN_STATUS_LABELS,
        WORDPRESS_SECURITY_LABELS,
        SSL_STATUS_LABELS,
        MONITOR_STATUS_LABELS, MONITOR_CHECK_LABELS, MONITOR_INCIDENT_LABELS,
    )
    from domains.models import Domain
    from hosting.models import HostingAccount
    from notifications.models import NotificationLog
    from monitoring.models import MonitorCheck, MonitorIncident, WebsiteMonitor
    from operations.models import InternalTask
    from support.models import SupportTicket
    from technical.models import (
        BackupRecord, Server, SSLCertificate, TechnicalAsset, WordPressPlugin, WordPressSite
    )
    from websites.models import WebsiteProject

    def items(choices, palette):
        return [
            {"value": value, "label": label, "tone": palette.get(value, "info")}
            for value, label in choices
        ]

    groups = [
        {"title": "Clientes", "icon": "groups", "items": items(ClientStatus.choices, CLIENT_STATUS_LABELS)},
        {"title": "Serviços", "icon": "inventory_2", "items": items(ServiceStatus.choices, SERVICE_STATUS_LABELS)},
        {"title": "Domínios", "icon": "language", "items": items(Domain.DomainStatus.choices, DOMAIN_STATUS_LABELS)},
        {"title": "Alojamentos", "icon": "dns", "items": items(HostingAccount.Status.choices, HOSTING_STATUS_LABELS)},
        {"title": "Servidores", "icon": "storage", "items": items(Server.Status.choices, SERVER_STATUS_LABELS)},
        {"title": "Ativos técnicos", "icon": "developer_board", "items": items(TechnicalAsset.Status.choices, ASSET_STATUS_LABELS)},
        {"title": "Backups", "icon": "backup", "items": items(BackupRecord.Status.choices, BACKUP_STATUS_LABELS)},
        {"title": "Sites WordPress", "icon": "web", "items": items(WordPressSite.HealthStatus.choices, WORDPRESS_HEALTH_LABELS)},
        {"title": "Plugins WordPress", "icon": "extension", "items": items(WordPressPlugin.ActivationStatus.choices, WORDPRESS_PLUGIN_STATUS_LABELS)},
        {"title": "Segurança de plugins", "icon": "bug_report", "items": items(WordPressPlugin.SecurityStatus.choices, WORDPRESS_SECURITY_LABELS)},
        {"title": "Certificados SSL", "icon": "lock", "items": items(SSLCertificate.Status.choices, SSL_STATUS_LABELS)},
        {"title": "Renovações", "icon": "event_repeat", "items": items(Renewal.Status.choices, RENEWAL_STATUS_LABELS)},
        {"title": "Pagamentos", "icon": "payments", "items": items(Payment.Status.choices, PAYMENT_STATUS_LABELS)},
        {"title": "Projetos web", "icon": "web", "items": items(WebsiteProject.Status.choices, WEBSITE_STATUS_LABELS)},
        {"title": "Tarefas", "icon": "task_alt", "items": items(InternalTask.Status.choices, TASK_STATUS_LABELS)},
        {"title": "Tickets", "icon": "support_agent", "items": items(SupportTicket.Status.choices, TICKET_STATUS_LABELS)},
        {"title": "Notificações", "icon": "notifications", "items": items(NotificationLog.Status.choices, NOTIFICATION_STATUS_LABELS)},
        {"title": "Monitores de websites", "icon": "monitor_heart", "items": items(WebsiteMonitor.Status.choices, MONITOR_STATUS_LABELS)},
        {"title": "Verificações de websites", "icon": "fact_check", "items": items(MonitorCheck.Result.choices, MONITOR_CHECK_LABELS)},
        {"title": "Incidentes de monitorização", "icon": "crisis_alert", "items": items(MonitorIncident.Status.choices, MONITOR_INCIDENT_LABELS)},
    ]
    context = {
        **admin.site.each_context(request),
        "title": "Legenda de estados",
        "groups": groups,
        "meanings": STATUS_MEANINGS,
    }
    return render(request, "admin/status_legend.html", context)


def demo_credentials_center(request):
    """Lista exclusivamente credenciais fictícias de demonstração.

    As palavras-passe reais nunca são recuperáveis no Django. Esta página usa o
    catálogo estático da base demo e só fica acessível a superutilizadores.
    """
    from django.core.exceptions import PermissionDenied
    from django.contrib.auth import get_user_model
    from core.demo_credentials import DEMO_PORTAL_ACCOUNTS

    if not request.user.is_superuser:
        raise PermissionDenied
    if not getattr(settings, "DEMO_CREDENTIALS_VISIBLE", False):
        raise PermissionDenied("A listagem de credenciais demo está desativada.")

    User = get_user_model()
    users = {user.username: user for user in User.objects.filter(username__in=[item["username"] for item in DEMO_PORTAL_ACCOUNTS])}
    rows = []
    for item in DEMO_PORTAL_ACCOUNTS:
        user = users.get(item["username"])
        rows.append({
            **item,
            "exists": bool(user),
            "active": bool(user and user.is_active),
            "last_login": user.last_login if user else None,
            "has_2fa": bool(user and hasattr(user, "totpdevice_set") and user.totpdevice_set.filter(confirmed=True).exists()),
        })
    context = {
        **admin.site.each_context(request),
        "title": "Credenciais de demonstração",
        "rows": rows,
        "warning": "Estas palavras-passe pertencem apenas a contas fictícias. Contas reais usam hashes e não podem ser apresentadas.",
    }
    return render(request, "admin/demo_credentials.html", context)


def demo_credentials_export(request):
    from django.core.exceptions import PermissionDenied
    from django.http import HttpResponse
    from core.demo_credentials import DEMO_PORTAL_ACCOUNTS

    if not request.user.is_superuser or not getattr(settings, "DEMO_CREDENTIALS_VISIBLE", False):
        raise PermissionDenied
    lines = [
        "KREATE4WEB — CREDENCIAIS FICTÍCIAS DE DEMONSTRAÇÃO",
        "=================================================",
        "Utilizar apenas em ambiente local/de testes.",
        "",
    ]
    for item in DEMO_PORTAL_ACCOUNTS:
        lines.extend([
            f"Cliente: {item['client_name']}",
            f"Utilizador: {item['username']}",
            f"Palavra-passe: {item['password']}",
            f"Perfil: {item['role_label']}",
            "-" * 48,
        ])
    response = HttpResponse("\n".join(lines), content_type="text/plain; charset=utf-8")
    response["Content-Disposition"] = 'attachment; filename="DEMO_CLIENT_CREDENTIALS_V8.txt"'
    return response


def superuser_demo_permission(request):
    return bool(
        request.user.is_authenticated
        and request.user.is_superuser
        and getattr(settings, "DEMO_CREDENTIALS_VISIBLE", False)
    )


def client_preview_permission(request):
    return bool(
        request.user.is_authenticated
        and request.user.is_staff
        and (request.user.is_superuser or request.user.has_perm("clients.view_clientportalaccess"))
    )


def client_preview_center(request):
    from django.core.exceptions import PermissionDenied
    from clients.models import ClientPortalAccess
    from client_portal.preview import preview_access

    if not client_preview_permission(request):
        raise PermissionDenied

    query = (request.GET.get("q") or "").strip()
    accesses = ClientPortalAccess.objects.select_related("client", "user").order_by("client__name", "user__username")
    if query:
        accesses = accesses.filter(
            Q(client__name__icontains=query)
            | Q(user__username__icontains=query)
            | Q(user__email__icontains=query)
            | Q(user__first_name__icontains=query)
            | Q(user__last_name__icontains=query)
        )
    current = preview_access(request)
    context = {
        **admin.site.each_context(request),
        "title": "Pré-visualizar como cliente",
        "accesses": accesses,
        "query": query,
        "current_preview": current,
        "version": getattr(settings, "K4W_VERSION", "9.4.0"),
    }
    return render(request, "admin/client_preview_center.html", context)


def client_preview_start(request, pk):
    from django.contrib import messages
    from django.core.exceptions import PermissionDenied
    from django.shortcuts import get_object_or_404, redirect
    from django.views.decorators.http import require_POST
    from clients.models import ClientPortalAccess
    from client_portal.preview import start_preview
    from security.models import SecurityEvent
    from security.services import log_event

    if request.method != "POST":
        raise PermissionDenied
    if not client_preview_permission(request):
        raise PermissionDenied
    access = get_object_or_404(ClientPortalAccess.objects.select_related("client", "user"), pk=pk)
    if not access.is_active or not access.client.portal_access_enabled or not access.user.is_active:
        messages.error(request, "Este acesso não está ativo e não pode ser pré-visualizado.")
        return redirect("client_preview_center")
    interactive = request.POST.get("mode") == "interactive"
    if interactive and not request.user.is_superuser:
        raise PermissionDenied("Apenas superutilizadores podem iniciar um teste interativo.")
    start_preview(request, access, interactive=interactive)
    log_event(
        request,
        SecurityEvent.EventType.PROFILE_UPDATED,
        user=request.user,
        details={
            "action": "client_preview_started",
            "client_id": access.client_id,
            "client": access.client.name,
            "portal_user_id": access.user_id,
            "portal_username": access.user.username,
            "interactive": interactive,
        },
    )
    mode_label = "teste interativo" if interactive else "pré-visualização só de leitura"
    messages.success(request, f"Iniciada {mode_label} para {access.client.name}.")
    return redirect("client_portal:dashboard")


def client_preview_stop(request):
    from django.contrib import messages
    from django.shortcuts import redirect
    from client_portal.preview import clear_preview, preview_access, preview_return_url
    from security.models import SecurityEvent
    from security.services import log_event

    if not request.user.is_authenticated or not request.user.is_staff:
        return redirect("security:login")
    access = preview_access(request)
    return_url = preview_return_url(request)
    if access:
        log_event(
            request,
            SecurityEvent.EventType.PROFILE_UPDATED,
            user=request.user,
            details={
                "action": "client_preview_ended",
                "client_id": access.client_id,
                "client": access.client.name,
                "portal_user_id": access.user_id,
                "portal_username": access.user.username,
            },
        )
    clear_preview(request)
    messages.info(request, "A pré-visualização do cliente foi terminada. A sua sessão administrativa continua ativa.")
    return redirect(return_url)
