"""Repository dashboard widgets — researchers, repo managers, reviewers."""
from __future__ import annotations

import json
from datetime import timedelta

from django.urls import reverse
from django.utils import timezone

from apps.core.dashboard.base import (
    ZONE_ACTIONS,
    ZONE_KPI,
    ZONE_PRIMARY,
    ZONE_SIDE,
    DashboardWidget,
)
from apps.core.dashboard.registry import register
from apps.core.dashboard.role_groups import (
    REPO_CONTRIBUTOR_ROLES,
    REPO_MANAGEMENT_ROLES,
    has_role,
)
from apps.core.permissions.roles import UserRole

# Repository "contribute" widgets target the dedicated RESEARCHER role plus
# the read+contribute repository personas (Knowledge Management Officer,
# Digital Academy Officer, Collaborator). Scholars already see learner +
# applicant widgets; stacking repo on top overloads their dashboard — they
# can still browse the repo via the menu.
ACTIVE_RESEARCHER_ROLES: tuple[str, ...] = (UserRole.RESEARCHER, *REPO_CONTRIBUTOR_ROLES)


def _doc_models():
    from apps.repository.documents.models import (
        AccessRequest,
        Document,
        DocumentReviewTask,
    )
    return Document, AccessRequest, DocumentReviewTask


# ── Researcher KPIs ──────────────────────────────────────────────────────


class _ResearcherKPIBase(DashboardWidget):
    zone = ZONE_KPI
    template = "core/dashboard/widgets/kpi_card.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, ACTIVE_RESEARCHER_ROLES)


@register
class ResearcherDocsKPI(_ResearcherKPIBase):
    key = "repo.kpi.my_documents"
    priority = 50

    def get_context(self, user) -> dict:
        Document, _, _ = _doc_models()
        n = Document.objects.filter(submitted_by=user).count()
        published = Document.objects.filter(submitted_by=user, status=Document.Status.PUBLISHED).count()
        return {
            "label": "My documents",
            "value": f"{n:,}",
            "hint": f"{published} published",
            "href": reverse("repo_documents:browse"),
        }


@register
class ResearcherDownloadsKPI(_ResearcherKPIBase):
    key = "repo.kpi.my_downloads"
    priority = 51

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, ACTIVE_RESEARCHER_ROLES)

    def get_context(self, user) -> dict:
        try:
            from apps.repository.analytics.models import DownloadEvent
            from django.db.models import Count
            from django.db.models.functions import TruncDate
        except Exception:  # noqa: BLE001
            return {"label": "Downloads (30d)", "value": "—"}

        Document, _, _ = _doc_models()
        my_doc_ids = list(Document.objects.filter(submitted_by=user).values_list("id", flat=True))
        if not my_doc_ids:
            return {"label": "Downloads (30d)", "value": "0", "hint": "Submit your first document"}
        start = (timezone.now() - timedelta(days=29)).replace(hour=0, minute=0, second=0, microsecond=0)
        rows = (
            DownloadEvent.objects.filter(document_id__in=my_doc_ids, timestamp__gte=start)
            .annotate(day=TruncDate("timestamp"))
            .values("day").annotate(c=Count("id")).order_by("day")
        )
        by_day = {str(r["day"]): r["c"] for r in rows}
        labels: list[str] = []
        values: list[int] = []
        total = 0
        for i in range(30):
            day = (start + timedelta(days=i)).date()
            v = by_day.get(str(day), 0)
            labels.append(day.strftime("%d"))
            values.append(v)
            total += v
        return {
            "label": "Downloads (30d)",
            "value": f"{total:,}",
            "hint": "Across your documents",
            "sparkline": {"labels_json": json.dumps(labels), "values_json": json.dumps(values)},
        }


@register
class ResearcherSavedSearchesKPI(_ResearcherKPIBase):
    key = "repo.kpi.saved_searches"
    priority = 52

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, ACTIVE_RESEARCHER_ROLES)

    def get_context(self, user) -> dict:
        try:
            from apps.repository.documents.models import SavedSearch

            n = SavedSearch.objects.filter(user=user).count()
        except Exception:  # noqa: BLE001
            n = 0
        return {
            "label": "Saved searches",
            "value": f"{n:,}",
            "hint": "Alerts to your inbox",
            "href": reverse("repo_documents:saved_search_list"),
        }


# ── Researcher: My documents list ────────────────────────────────────────


@register
class MyDocumentsWidget(DashboardWidget):
    key = "repo.my_documents"
    zone = ZONE_PRIMARY
    priority = 50
    template = "core/dashboard/widgets/list_card.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, ACTIVE_RESEARCHER_ROLES)

    def get_context(self, user) -> dict:
        Document, _, _ = _doc_models()
        qs = Document.objects.filter(submitted_by=user).order_by("-id")[:6]
        items = []
        color_map = {
            Document.Status.DRAFT: "neutral",
            Document.Status.SUBMITTED: "amber",
            Document.Status.UNDER_QA: "sky",
            Document.Status.REVISION_REQUIRED: "rose",
            Document.Status.APPROVED: "emerald",
            Document.Status.PUBLISHED: "emerald",
            Document.Status.WITHDRAWN: "neutral",
            Document.Status.REJECTED: "rose",
        }
        for d in qs:
            items.append({
                "label": d.title[:80],
                "sublabel": d.get_document_type_display(),
                "badge": d.get_status_display(),
                "badge_color": color_map.get(d.status, "neutral"),
                "href": reverse("repo_documents:document_detail", kwargs={"uid": d.document_uid}),
            })
        return {
            "title": "Your documents",
            "items": items,
            "empty_text": "You haven't submitted any documents yet.",
            "footer_href": reverse("repo_documents:browse"),
            "footer_label": "Browse repository",
        }


# ── Researcher: My docs by status donut ──────────────────────────────────


@register
class ResearcherDocsDonut(DashboardWidget):
    key = "repo.my_docs_donut"
    zone = ZONE_PRIMARY
    priority = 55
    template = "core/dashboard/widgets/donut_card.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, ACTIVE_RESEARCHER_ROLES)

    def get_context(self, user) -> dict:
        from django.db.models import Count
        Document, _, _ = _doc_models()
        rows = (
            Document.objects.filter(submitted_by=user)
            .values("status").annotate(c=Count("id"))
        )
        palette = {
            Document.Status.DRAFT: ("Draft", "#cbb98a"),
            Document.Status.SUBMITTED: ("Submitted", "#d97706"),
            Document.Status.UNDER_QA: ("Under QA", "#0284c7"),
            Document.Status.REVISION_REQUIRED: ("Revision required", "#e11d48"),
            Document.Status.APPROVED: ("Approved", "#7c3aed"),
            Document.Status.PUBLISHED: ("Published", "#059669"),
            Document.Status.WITHDRAWN: ("Withdrawn", "#94a3b8"),
            Document.Status.REJECTED: ("Rejected", "#475569"),
        }
        segs, labels, values, colors = [], [], [], []
        total = 0
        for r in rows:
            if r["status"] not in palette:
                continue
            label, color = palette[r["status"]]
            segs.append({"label": label, "value": r["c"], "color": color})
            labels.append(label)
            values.append(r["c"])
            colors.append(color)
            total += r["c"]
        return {
            "title": "Your documents by status",
            "segments": segs,
            "labels_json": json.dumps(labels),
            "values_json": json.dumps(values),
            "colors_json": json.dumps(colors),
            "total": total,
            "total_label": "documents",
        }


# ── Researcher: Top viewed / downloaded ──────────────────────────────────


@register
class ResearcherTopDocsWidget(DashboardWidget):
    key = "repo.top_my_docs"
    zone = ZONE_SIDE
    priority = 55
    template = "core/dashboard/widgets/list_card.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, ACTIVE_RESEARCHER_ROLES)

    def get_context(self, user) -> dict:
        Document, _, _ = _doc_models()
        items: list = []
        try:
            from apps.repository.analytics.models import DownloadEvent
            from django.db.models import Count
            top = (
                DownloadEvent.objects
                .filter(document__submitted_by=user)
                .values("document_id", "document__title", "document__document_uid")
                .annotate(c=Count("id"))
                .order_by("-c")[:5]
            )
            for row in top:
                items.append({
                    "label": row["document__title"][:80],
                    "sublabel": f"{row['c']:,} downloads",
                    "value": f"{row['c']:,}",
                    "href": reverse("repo_documents:document_detail", kwargs={"uid": row["document__document_uid"]}),
                })
        except Exception:  # noqa: BLE001
            pass
        return {
            "title": "Top downloaded",
            "subtitle": "Your most popular documents",
            "items": items,
            "empty_text": "No download data yet.",
        }


# ── Researcher quick actions ─────────────────────────────────────────────


@register
class ResearcherQuickActions(DashboardWidget):
    key = "repo.researcher_actions"
    zone = ZONE_ACTIONS
    priority = 50
    template = "core/dashboard/widgets/quick_actions.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, ACTIVE_RESEARCHER_ROLES)

    def get_context(self, user) -> dict:
        return {
            "actions": [
                {
                    "label": "Submit document",
                    "href": reverse("repo_documents:submit"),
                    "primary": True,
                    "icon": "M12 4v16m8-8H4",
                },
                {
                    "label": "Browse repository",
                    "href": reverse("repo_documents:browse"),
                    "icon": "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z",
                },
                {
                    "label": "Saved searches",
                    "href": reverse("repo_documents:saved_search_list"),
                    "icon": "M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z",
                },
            ],
        }


# ── Repo manager: QA queue ───────────────────────────────────────────────


@register
class RepoQAQueueWidget(DashboardWidget):
    key = "repo.qa_queue"
    zone = ZONE_PRIMARY
    priority = 60
    template = "core/dashboard/widgets/list_card.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, REPO_MANAGEMENT_ROLES)

    def get_context(self, user) -> dict:
        Document, _, DocumentReviewTask = _doc_models()
        # First, documents in UNDER_QA status awaiting review
        docs = (
            Document.objects.filter(status=Document.Status.UNDER_QA)
            .order_by("-id")[:5]
        )
        items = []
        for d in docs:
            items.append({
                "label": d.title[:80],
                "sublabel": d.get_document_type_display(),
                "badge": "QA",
                "badge_color": "sky",
                "href": reverse("repo_documents:document_detail", kwargs={"uid": d.document_uid}),
            })
        return {
            "title": "QA review queue",
            "subtitle": "Documents awaiting QA",
            "items": items,
            "empty_text": "No documents waiting for QA.",
            "footer_href": reverse("repo_documents:qa_queue"),
            "footer_label": "Open QA queue",
        }


# ── Repo manager: Access requests ────────────────────────────────────────


@register
class AccessRequestsWidget(DashboardWidget):
    key = "repo.access_requests"
    zone = ZONE_SIDE
    priority = 50
    template = "core/dashboard/widgets/list_card.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, REPO_MANAGEMENT_ROLES)

    def get_context(self, user) -> dict:
        _, AccessRequest, _ = _doc_models()
        qs = (
            AccessRequest.objects.filter(status=AccessRequest.Status.PENDING)
            .select_related("document", "requester")
            .order_by("-created_at")[:5]
        )
        items = []
        for r in qs:
            requester = r.requester.get_full_name() or r.requester.email
            items.append({
                "label": r.document.title[:60],
                "sublabel": f"Requested by {requester}",
                "badge": "Pending",
                "badge_color": "amber",
                "href": reverse("repo_documents:access_request_queue"),
            })
        total = AccessRequest.objects.filter(status=AccessRequest.Status.PENDING).count()
        return {
            "title": "Access requests",
            "subtitle": f"{total} pending" if total else "No pending requests",
            "items": items,
            "empty_text": "No access requests waiting.",
            "footer_href": reverse("repo_documents:access_request_queue"),
            "footer_label": "Open queue",
        }


# ── Repo manager KPIs ────────────────────────────────────────────────────


class _RepoMgrKPIBase(DashboardWidget):
    zone = ZONE_KPI
    template = "core/dashboard/widgets/kpi_card.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, REPO_MANAGEMENT_ROLES)


@register
class RepoTotalDocumentsKPI(_RepoMgrKPIBase):
    key = "repo.kpi.total_documents"
    priority = 60

    def get_context(self, user) -> dict:
        Document, _, _ = _doc_models()
        total = Document.objects.count()
        published = Document.objects.filter(status=Document.Status.PUBLISHED).count()
        return {
            "label": "Documents",
            "value": f"{total:,}",
            "hint": f"{published} published",
            "href": reverse("repo_documents:browse"),
        }


@register
class RepoUnderQAKPI(_RepoMgrKPIBase):
    key = "repo.kpi.under_qa"
    priority = 61

    def get_context(self, user) -> dict:
        Document, _, _ = _doc_models()
        n = Document.objects.filter(status=Document.Status.UNDER_QA).count()
        return {
            "label": "Awaiting QA",
            "value": f"{n:,}",
            "accent": "sky" if n else "primary",
            "hint": "In QA review",
            "href": reverse("repo_documents:qa_queue"),
        }


@register
class RepoAccessRequestsKPI(_RepoMgrKPIBase):
    key = "repo.kpi.access_requests"
    priority = 62

    def get_context(self, user) -> dict:
        _, AccessRequest, _ = _doc_models()
        n = AccessRequest.objects.filter(status=AccessRequest.Status.PENDING).count()
        return {
            "label": "Access requests",
            "value": f"{n:,}",
            "accent": "amber" if n else "primary",
            "hint": "Pending decision",
            "href": reverse("repo_documents:access_request_queue"),
        }


@register
class RepoTotalDownloadsKPI(_RepoMgrKPIBase):
    key = "repo.kpi.downloads"
    priority = 63

    def get_context(self, user) -> dict:
        try:
            from apps.repository.analytics.models import DownloadEvent
            from django.db.models import Count
            from django.db.models.functions import TruncDate
        except Exception:  # noqa: BLE001
            return {"label": "Downloads (30d)", "value": "—"}

        start = (timezone.now() - timedelta(days=29)).replace(hour=0, minute=0, second=0, microsecond=0)
        rows = (
            DownloadEvent.objects.filter(timestamp__gte=start)
            .annotate(day=TruncDate("timestamp"))
            .values("day").annotate(c=Count("id")).order_by("day")
        )
        by_day = {str(r["day"]): r["c"] for r in rows}
        labels: list[str] = []
        values: list[int] = []
        total = 0
        for i in range(30):
            day = (start + timedelta(days=i)).date()
            v = by_day.get(str(day), 0)
            labels.append(day.strftime("%d"))
            values.append(v)
            total += v
        return {
            "label": "Downloads (30d)",
            "value": f"{total:,}",
            "hint": "All documents",
            "href": reverse("repo_analytics:usage_dashboard"),
            "sparkline": {"labels_json": json.dumps(labels), "values_json": json.dumps(values)},
        }


# ── Repo manager: Documents-by-status donut ──────────────────────────────


@register
class RepoDocsByStatusDonut(DashboardWidget):
    key = "repo.docs_by_status_donut"
    zone = ZONE_PRIMARY
    priority = 65
    template = "core/dashboard/widgets/donut_card.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, REPO_MANAGEMENT_ROLES)

    def get_context(self, user) -> dict:
        from django.db.models import Count

        Document, _, _ = _doc_models()
        rows = Document.objects.values("status").annotate(c=Count("id")).order_by()
        palette = {
            Document.Status.DRAFT: ("Draft", "#cbb98a"),
            Document.Status.SUBMITTED: ("Submitted", "#d97706"),
            Document.Status.UNDER_QA: ("Under QA", "#0284c7"),
            Document.Status.REVISION_REQUIRED: ("Revision required", "#e11d48"),
            Document.Status.APPROVED: ("Approved", "#7c3aed"),
            Document.Status.PUBLISHED: ("Published", "#059669"),
            Document.Status.WITHDRAWN: ("Withdrawn", "#94a3b8"),
            Document.Status.REJECTED: ("Rejected", "#475569"),
        }
        segs, labels, values, colors = [], [], [], []
        total = 0
        for r in rows:
            if r["status"] not in palette:
                continue
            label, color = palette[r["status"]]
            segs.append({"label": label, "value": r["c"], "color": color})
            labels.append(label)
            values.append(r["c"])
            colors.append(color)
            total += r["c"]
        order = sorted(range(len(segs)), key=lambda i: -segs[i]["value"])
        segs = [segs[i] for i in order]
        labels = [labels[i] for i in order]
        values = [values[i] for i in order]
        colors = [colors[i] for i in order]
        return {
            "title": "Documents by status",
            "segments": segs,
            "labels_json": json.dumps(labels),
            "values_json": json.dumps(values),
            "colors_json": json.dumps(colors),
            "total": total,
            "total_label": "documents",
            "footer_href": reverse("repo_documents:browse"),
            "footer_label": "Browse repository",
        }


@register
class RepoTopDownloadedWidget(DashboardWidget):
    """Most-downloaded documents over the last 30 days — repo managers only."""

    key = "repo.top_downloaded"
    zone = ZONE_PRIMARY
    priority = 70
    template = "core/dashboard/widgets/list_card.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, REPO_MANAGEMENT_ROLES)

    def get_context(self, user) -> dict:
        items: list = []
        try:
            from django.db.models import Count

            from apps.repository.analytics.models import DownloadEvent
        except Exception:  # noqa: BLE001
            return {
                "title": "Top downloaded (30d)",
                "subtitle": "Most-downloaded documents",
                "items": [],
                "empty_text": "Analytics not available.",
            }

        start = timezone.now() - timedelta(days=30)
        rows = (
            DownloadEvent.objects.filter(timestamp__gte=start)
            .values("document_id", "document__title", "document__document_uid", "document__document_type")
            .annotate(c=Count("id"))
            .order_by("-c")[:6]
        )
        Document, _, _ = _doc_models()
        type_label = dict(Document.DocumentType.choices)
        for row in rows:
            items.append({
                "label": (row["document__title"] or "—")[:80],
                "sublabel": type_label.get(row["document__document_type"], ""),
                "value": f"{row['c']:,}",
                "href": reverse(
                    "repo_documents:document_detail",
                    kwargs={"uid": row["document__document_uid"]},
                ),
            })
        return {
            "title": "Top downloaded",
            "subtitle": "Most-downloaded documents (last 30 days)",
            "items": items,
            "empty_text": "No downloads recorded in the last 30 days.",
            "footer_href": reverse("repo_analytics:usage_dashboard"),
            "footer_label": "Open analytics",
        }


@register
class RepoRecentSubmissionsWidget(DashboardWidget):
    key = "repo.recent_submissions"
    zone = ZONE_SIDE
    priority = 60
    template = "core/dashboard/widgets/list_card.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, REPO_MANAGEMENT_ROLES)

    def get_context(self, user) -> dict:
        Document, _, _ = _doc_models()
        qs = Document.objects.exclude(status=Document.Status.DRAFT).order_by("-id")[:6]
        items = []
        color_map = {
            Document.Status.SUBMITTED: "amber",
            Document.Status.UNDER_QA: "sky",
            Document.Status.REVISION_REQUIRED: "rose",
            Document.Status.APPROVED: "emerald",
            Document.Status.PUBLISHED: "emerald",
            Document.Status.WITHDRAWN: "neutral",
            Document.Status.REJECTED: "rose",
        }
        for d in qs:
            items.append({
                "label": d.title[:80],
                "sublabel": d.get_document_type_display(),
                "badge": d.get_status_display(),
                "badge_color": color_map.get(d.status, "neutral"),
                "href": reverse("repo_documents:document_detail", kwargs={"uid": d.document_uid}),
            })
        return {
            "title": "Recent submissions",
            "items": items,
            "empty_text": "No submissions yet.",
        }


# ── Repo manager: Quick actions ──────────────────────────────────────────


@register
class RepoManagerQuickActions(DashboardWidget):
    key = "repo.manager_actions"
    zone = ZONE_ACTIONS
    priority = 45
    template = "core/dashboard/widgets/quick_actions.html"

    @classmethod
    def is_enabled(cls, user) -> bool:
        return has_role(user, REPO_MANAGEMENT_ROLES)

    def get_context(self, user) -> dict:
        return {
            "actions": [
                {
                    "label": "QA queue",
                    "href": reverse("repo_documents:qa_queue"),
                    "primary": True,
                    "icon": "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z",
                },
                {
                    "label": "Access requests",
                    "href": reverse("repo_documents:access_request_queue"),
                    "icon": "M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z",
                },
                {
                    "label": "Collections",
                    "href": reverse("repo_documents:collection_list"),
                    "icon": "M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10",
                },
                {
                    "label": "Analytics",
                    "href": reverse("repo_analytics:usage_dashboard"),
                    "icon": "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z",
                },
            ],
        }
