from __future__ import annotations

from datetime import date, datetime, timedelta
from decimal import Decimal
from urllib.parse import urlencode

from django.core.exceptions import FieldDoesNotExist
from django.db.models import QuerySet
from django.urls import NoReverseMatch, reverse
from django.utils import timezone
from django.utils.formats import date_format


TONE_BY_STATUS = {
    "active": "success",
    "paid": "success",
    "renewed": "success",
    "verified": "success",
    "resolved": "success",
    "closed": "neutral",
    "done": "success",
    "sent": "success",
    "approved": "success",
    "special": "primary",
    "delivered": "success",
    "in_progress": "primary",
    "development": "primary",
    "scheduled": "primary",
    "pending": "warning",
    "to_notify": "warning",
    "notified": "primary",
    "waiting_payment": "warning",
    "partial": "warning",
    "waiting_client": "warning",
    "waiting_internal": "warning",
    "waiting": "warning",
    "attention": "warning",
    "maintenance": "warning",
    "expiring": "warning",
    "overdue": "danger",
    "expired": "danger",
    "failed": "danger",
    "rejected": "danger",
    "urgent": "danger",
    "debt": "danger",
    "cancelled": "neutral",
    "archived": "neutral",
    "inactive": "neutral",
    "retired": "neutral",
    "transferred": "neutral",
    "unknown": "neutral",
    "draft": "neutral",
    "quote": "neutral",
    "provisioning": "primary",
    "processing": "primary",
    "validating": "primary",
    "sending": "primary",
    "review": "primary",
    "transfer_in": "primary",
    "migrating": "primary",
    "reopened": "primary",
    "running": "primary",
    "grace": "warning",
    "limited": "warning",
    "degraded": "warning",
    "on_hold": "warning",
    "waiting_proof": "warning",
    "created": "warning",
    "blocked": "danger",
    "incident": "danger",
    "offline": "danger",
    "critical": "danger",
    "suspended": "danger",
    "suppressed": "warning",
    "refunded": "neutral",
    "complimentary": "neutral",
    "client_direct": "neutral",
}


class PortalAdminMixin:
    """Camada visual e contextual comum a todo o backoffice.

    Mantém as funcionalidades nativas do Django Admin/Unfold, acrescentando
    cabeçalhos editoriais, indicadores, atalhos e metadados consistentes.
    """

    list_before_template = "admin/includes/portal_list_header.html"
    change_form_before_template = "admin/includes/portal_form_header.html"
    warn_unsaved_form = True
    list_per_page = 25
    list_max_show_all = 150

    portal_icon = "database"
    portal_kicker = "GESTÃO"
    portal_description = "Consulte, filtre e mantenha esta informação atualizada."
    portal_tone = "blue"
    portal_stats: tuple[dict, ...] = ()
    portal_related_links: tuple[dict, ...] = ()
    portal_use_fieldset_tabs = True

    def get_portal_title(self, request):
        return str(self.model._meta.verbose_name_plural).title()

    def get_portal_description(self, request):
        return self.portal_description

    def get_portal_stats(self, request, queryset: QuerySet):
        specs = self.portal_stats or (
            {
                "label": "Total de registos",
                "icon": self.portal_icon,
                "tone": "primary",
                "caption": "Informação registada",
            },
        )
        cards = []
        for spec in specs:
            card = dict(spec)
            filtered = queryset
            filters = card.pop("filters", None)
            exclude = card.pop("exclude", None)
            method = card.pop("method", None)
            if filters:
                filtered = filtered.filter(**filters)
            if exclude:
                filtered = filtered.exclude(**exclude)
            if method:
                value = getattr(self, method)(request, queryset)
            else:
                value = filtered.count()
            card["value"] = value
            card.setdefault("icon", "analytics")
            card.setdefault("tone", "primary")
            card.setdefault("caption", "Abrir listagem")
            query = card.pop("query", None)
            if query:
                card["url"] = f"?{urlencode(query, doseq=True)}"
            else:
                card["url"] = ""
            cards.append(card)
        return cards

    def get_portal_related_links(self, request, obj=None):
        links = []
        for item in self.portal_related_links:
            link = dict(item)
            url_name = link.pop("url_name", None)
            if url_name:
                try:
                    link["url"] = reverse(url_name)
                except NoReverseMatch:
                    continue
            links.append(link)
        return links

    def get_portal_add_url(self, request):
        if not self.has_add_permission(request):
            return None
        try:
            return reverse(f"admin:{self.model._meta.app_label}_{self.model._meta.model_name}_add")
        except NoReverseMatch:
            return None

    def get_fieldsets(self, request, obj=None):
        fieldsets = super().get_fieldsets(request, obj)
        if not self.portal_use_fieldset_tabs or len(fieldsets) <= 1:
            return fieldsets

        transformed = []
        for name, options in fieldsets:
            options = dict(options)
            classes = list(options.get("classes", ()))
            if name and "tab" not in classes:
                classes.append("tab")
            options["classes"] = tuple(classes)
            transformed.append((name, options))
        return transformed

    def _has_model_field(self, name):
        try:
            self.model._meta.get_field(name)
            return True
        except FieldDoesNotExist:
            return False

    @staticmethod
    def _format_meta_value(value):
        if value is None or value == "":
            return "—"
        if isinstance(value, datetime):
            return timezone.localtime(value).strftime("%d/%m/%Y · %H:%M")
        if isinstance(value, date):
            return date_format(value, "d/m/Y")
        if isinstance(value, Decimal):
            return f"{value:.2f} €"
        if isinstance(value, bool):
            return "Sim" if value else "Não"
        return str(value)

    def _object_value(self, obj, field_name):
        display_method = getattr(obj, f"get_{field_name}_display", None)
        if callable(display_method):
            return display_method()
        value = obj
        for part in field_name.split("__"):
            value = getattr(value, part, None)
            if value is None:
                break
            if callable(value):
                value = value()
        return value

    def get_portal_object_meta(self, request, obj):
        if obj is None:
            return [
                {"label": "Estado", "value": "Novo registo", "icon": "add_circle"},
                {"label": "Validação", "value": "Campos obrigatórios assinalados", "icon": "verified"},
            ]

        candidates = []
        for label, field_name, icon in (
            ("Cliente", "client", "person"),
            ("Estado", "status", "sell"),
            ("Próxima renovação", "next_renewal_date", "event_repeat"),
            ("Vencimento", "due_date", "event"),
            ("Valor", "sale_price", "payments"),
            ("Atualizado", "updated_at", "update"),
        ):
            if hasattr(obj, field_name):
                candidates.append(
                    {
                        "label": label,
                        "value": self._format_meta_value(self._object_value(obj, field_name)),
                        "icon": icon,
                    }
                )
            if len(candidates) == 4:
                break
        return candidates

    def get_portal_status(self, obj):
        if obj is None:
            return {"label": "Novo", "tone": "primary", "raw": "new"}
        if hasattr(obj, "status"):
            raw = getattr(obj, "status")
            label = self._object_value(obj, "status")
            return {"label": str(label), "tone": TONE_BY_STATUS.get(str(raw), "primary"), "raw": str(raw)}
        if hasattr(obj, "is_active"):
            raw = bool(getattr(obj, "is_active"))
            return {"label": "Ativo" if raw else "Inativo", "tone": "success" if raw else "neutral", "raw": str(raw).lower()}
        return None

    def _portal_list_context(self, request):
        queryset = self.get_queryset(request)
        return {
            "title": self.get_portal_title(request),
            "description": self.get_portal_description(request),
            "kicker": self.portal_kicker,
            "icon": self.portal_icon,
            "tone": self.portal_tone,
            "stats": self.get_portal_stats(request, queryset),
            "links": self.get_portal_related_links(request),
            "add_url": self.get_portal_add_url(request),
            "add_label": f"Novo {self.model._meta.verbose_name}",
            "total": queryset.count(),
        }

    def changelist_view(self, request, extra_context=None):
        context = dict(extra_context or {})
        context["portal_ui"] = self._portal_list_context(request)
        return super().changelist_view(request, extra_context=context)

    def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
        obj = self.get_object(request, object_id) if object_id else None
        context = dict(extra_context or {})
        context["portal_ui"] = {
            "title": str(obj) if obj else f"Novo {self.model._meta.verbose_name}",
            "description": self.get_portal_description(request),
            "kicker": self.portal_kicker,
            "icon": self.portal_icon,
            "tone": self.portal_tone,
            "status": self.get_portal_status(obj),
            "meta": self.get_portal_object_meta(request, obj),
            "links": self.get_portal_related_links(request, obj),
            "is_add": obj is None,
            "list_url": reverse(f"admin:{self.model._meta.app_label}_{self.model._meta.model_name}_changelist"),
            "list_label": str(self.model._meta.verbose_name_plural).title(),
        }
        return super().changeform_view(request, object_id, form_url, extra_context=context)
