from django.contrib import messages
from django.db.models import Count, Q
from django.shortcuts import redirect
from django.views.generic import CreateView, DetailView, ListView, UpdateView

from apps.core.audit.models import AuditLog
from apps.core.countries import get_country_name
from apps.core.lists import CsvExportMixin, SortableListMixin
from apps.core.authentication.models import Institution
from apps.core.permissions.mixins import AuditorReadOnlyMixin, RimsAccessMixin
from apps.core.permissions.roles import UserRole
from apps.rims.grants import rims_perms
from apps.rims.grants.models import StrategicObjective
from apps.rims.operations.forms import (
    DonorForm,
    InstitutionForm,
    MOUForm,
    PartnerForm,
    StrategicObjectiveForm,
)
from apps.rims.operations.models import MOU, Donor, MemberUniversity, Partner


class GrantsStaffMixin(RimsAccessMixin):
    allowed_roles = (
        UserRole.ADMIN,
        UserRole.GRANTS_MANAGER,
    )
    rims_permissions = (rims_perms.MANAGE_OPERATIONS, rims_perms.MANAGE_CALLS)


class PartnerListView(GrantsStaffMixin, SortableListMixin, CsvExportMixin, ListView):
    model = Partner
    template_name = "operations/partner_list.html"
    context_object_name = "partners"
    paginate_by = 12

    sortable_fields = {
        "name": "name",
        "country": "country",
        "created": "created_at",
    }
    default_sort = "name"

    csv_columns = (
        ("Name", "name"),
        ("Type", "get_partner_type_display"),
        ("Country", lambda p: get_country_name(p.country)),
        ("Contact email", "contact_email"),
        ("Website", "website"),
        ("Created", lambda p: p.created_at.isoformat() if p.created_at else ""),
    )
    csv_filename = "partners"

    def get_queryset(self):
        qs = Partner.objects.all()
        q = (self.request.GET.get("q") or "").strip()
        if q:
            qs = qs.filter(Q(name__icontains=q) | Q(country__icontains=q))
        return self.apply_sort(qs)

    def get_template_names(self):
        if getattr(self.request, "htmx", False):
            return ["operations/partials/partner_list_results.html"]
        return super().get_template_names()


class PartnerDetailView(GrantsStaffMixin, DetailView):
    model = Partner
    template_name = "operations/partner_detail.html"
    context_object_name = "partner"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        partner = self.object
        # Lazy imports — avoid circular FK imports at module load.
        from apps.rims.grants.models import FundingRecord, GrantCall

        mous_qs = partner.mous.select_related("institution")
        members_qs = partner.member_institutions.select_related("institution")
        grant_calls_qs = GrantCall.objects.filter(partner=partner)
        funding_qs = FundingRecord.objects.filter(partner=partner)
        donor_profiles_qs = partner.donor_profiles.all()

        ctx["mou_count"] = mous_qs.count()
        ctx["mou_active_count"] = mous_qs.filter(status=MOU.Status.ACTIVE).count()
        ctx["member_institution_count"] = members_qs.count()
        ctx["grant_call_count"] = grant_calls_qs.count()
        ctx["funding_record_count"] = funding_qs.count()
        ctx["donor_profile_count"] = donor_profiles_qs.count()
        ctx["recent_mous"] = mous_qs.order_by("-signed_on", "-created_at")[:5]
        ctx["recent_members"] = members_qs.order_by("-joined_on")[:6]
        ctx["recent_grant_calls"] = grant_calls_qs.order_by("-opens_at", "-id")[:5]
        ctx["recent_funding_records"] = funding_qs.order_by("-start_date", "-id")[:5]
        ctx["donor_profiles"] = donor_profiles_qs[:4]
        return ctx


class InstitutionListView(GrantsStaffMixin, SortableListMixin, CsvExportMixin, ListView):
    model = Institution
    template_name = "operations/institution_list.html"
    context_object_name = "institutions"
    paginate_by = 12

    sortable_fields = {
        "name": "name",
        "country": "country",
    }
    default_sort = "name"

    csv_columns = (
        ("Name", "name"),
        ("Country", lambda i: get_country_name(i.country)),
        ("Contact email", "contact_email"),
    )
    csv_filename = "institutions"

    def get_queryset(self):
        qs = super().get_queryset()
        q = (self.request.GET.get("q") or "").strip()
        if q:
            # `country` stores the full name now, so match it directly.
            qs = qs.filter(
                Q(name__icontains=q) | Q(region__icontains=q) | Q(country__icontains=q)
            )
        return self.apply_sort(qs)

    def get_template_names(self):
        # Live-search swaps only the card grid; without this an HTMX request
        # would return the full page and nest it inside #institution-cards.
        if getattr(self.request, "htmx", False):
            return ["operations/partials/institution_list_results.html"]
        return super().get_template_names()


class MOUListView(GrantsStaffMixin, SortableListMixin, CsvExportMixin, ListView):
    model = MOU
    template_name = "operations/mou_list.html"
    context_object_name = "mous"
    paginate_by = 25

    sortable_fields = {
        "title": "title",
        "signed": "signed_on",
        "status": "status",
        "partner": "partner__name",
    }
    default_sort = "-signed_on"

    csv_columns = (
        ("Title", "title"),
        ("Partner", "partner.name"),
        ("Signed on", lambda m: m.signed_on.isoformat() if m.signed_on else ""),
        ("Expires on", lambda m: m.expires_on.isoformat() if m.expires_on else ""),
        ("Status", "get_status_display"),
    )
    csv_filename = "mous"

    def get_queryset(self):
        qs = MOU.objects.select_related("partner")
        institution_id = (self.request.GET.get("institution") or "").strip()
        if institution_id.isdigit():
            qs = qs.filter(institution_id=int(institution_id))
        q = (self.request.GET.get("q") or "").strip()
        if q:
            qs = qs.filter(Q(title__icontains=q) | Q(partner__name__icontains=q))
        return self.apply_sort(qs)

    def get_template_names(self):
        if getattr(self.request, "htmx", False):
            return ["operations/partials/mou_list_results.html"]
        return super().get_template_names()


class PartnerCreateView(GrantsStaffMixin, CreateView):
    model = Partner
    form_class = PartnerForm
    template_name = "operations/partner_form.html"

    def form_valid(self, form):
        self.object = form.save()
        messages.success(self.request, "Partner added.")
        return redirect("rims_operations:partner_detail", pk=self.object.pk)


class InstitutionCreateView(GrantsStaffMixin, CreateView):
    model = Institution
    form_class = InstitutionForm
    template_name = "operations/institution_form.html"

    def form_valid(self, form):
        self.object = form.save()
        messages.success(self.request, "Institution added.")
        return redirect("rims_operations:institution_list")


class MOUCreateView(GrantsStaffMixin, CreateView):
    model = MOU
    form_class = MOUForm
    template_name = "operations/mou_form.html"

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.created_by = self.request.user
        self.object.save()
        messages.success(self.request, "MOU created.")
        return redirect("rims_operations:mou_detail", pk=self.object.pk)


class PartnerUpdateView(GrantsStaffMixin, UpdateView):
    model = Partner
    form_class = PartnerForm
    template_name = "operations/partner_edit.html"

    def form_valid(self, form):
        self.object = form.save()
        messages.success(self.request, "Partner updated.")
        return redirect("rims_operations:partner_detail", pk=self.object.pk)


class InstitutionDetailView(GrantsStaffMixin, DetailView):
    model = Institution
    template_name = "operations/institution_detail.html"
    context_object_name = "institution"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        institution = self.object
        # Lazy imports — avoid circular FK imports at module load.
        from apps.rims.grants.models import Application
        from apps.rims.scholarships.models import Scholar

        ctx["scholar_count"] = Scholar.objects.filter(institution=institution).count()
        ctx["scholar_active_count"] = Scholar.objects.filter(
            institution=institution, graduation__isnull=True,
        ).count()
        ctx["user_count"] = institution.users.count()
        ctx["application_count"] = (
            Application.objects.filter(institution=institution)
            .exclude(status=Application.Status.DRAFT)
            .count()
        )
        ctx["mou_count"] = institution.mous.count()
        ctx["mou_active_count"] = institution.mous.filter(status=MOU.Status.ACTIVE).count()
        ctx["partner_link_count"] = institution.partner_links.count()
        ctx["recent_users"] = (
            institution.users.order_by("-date_joined")[:6]
        )
        ctx["recent_partners"] = (
            institution.partner_links.select_related("partner").order_by("-joined_on")[:6]
        )
        return ctx


class InstitutionUpdateView(GrantsStaffMixin, UpdateView):
    model = Institution
    form_class = InstitutionForm
    template_name = "operations/institution_edit.html"

    def form_valid(self, form):
        self.object = form.save()
        messages.success(self.request, "Institution updated.")
        return redirect("rims_operations:institution_detail", pk=self.object.pk)


class MOUDetailView(GrantsStaffMixin, DetailView):
    model = MOU
    template_name = "operations/mou_detail.html"
    context_object_name = "mou"

    def get_queryset(self):
        return MOU.objects.select_related("partner", "institution")


class MOUUpdateView(GrantsStaffMixin, UpdateView):
    model = MOU
    form_class = MOUForm
    template_name = "operations/mou_edit.html"

    def form_valid(self, form):
        self.object = form.save()
        messages.success(self.request, "MOU updated.")
        return redirect("rims_operations:mou_detail", pk=self.object.pk)


# ---------------------------------------------------------------------------
# PRD §5.1 FRFA-GPS007/008/009 — Donor staff CRUD with duplicate detection.
# ---------------------------------------------------------------------------
class DonorListView(GrantsStaffMixin, SortableListMixin, CsvExportMixin, ListView):
    model = Donor
    template_name = "operations/donor_list.html"
    context_object_name = "donors"
    paginate_by = 25

    sortable_fields = {
        "name": "name",
        "type": "donor_type",
        "country": "country",
        "created": "created_at",
    }
    default_sort = "name"

    csv_columns = (
        ("Name", "name"),
        ("Type", "get_donor_type_display"),
        ("Country", lambda d: get_country_name(d.country)),
        ("Email", "contact_email"),
        ("Phone", "contact_phone"),
        ("Website", "website"),
        ("Created", lambda d: d.created_at.isoformat() if d.created_at else ""),
    )
    csv_filename = "donors"

    def get_queryset(self):
        qs = Donor.objects.all()
        q = (self.request.GET.get("q") or "").strip()
        self._search = q
        self._type_filter = (self.request.GET.get("type") or "").strip()
        if q:
            qs = qs.filter(
                Q(name__icontains=q)
                | Q(contact_email__icontains=q)
                | Q(country__icontains=q)
            )
        if self._type_filter:
            qs = qs.filter(donor_type=self._type_filter)
        return self.apply_sort(qs)

    def get_template_names(self):
        if getattr(self.request, "htmx", False):
            return ["operations/partials/donor_list_results.html"]
        return super().get_template_names()

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        active = getattr(self, "_type_filter", "") or ""
        counts = {row["donor_type"]: row["n"] for row in Donor.objects.values("donor_type").annotate(n=Count("id"))}
        facets = [{
            "label": "All",
            "href": self.request.path,
            "count": Donor.objects.count(),
            "active": not active,
        }]
        for value, label in Donor.DonorType.choices:
            facets.append({
                "label": label,
                "href": f"{self.request.path}?type={value}",
                "count": counts.get(value, 0),
                "active": active == value,
            })
        ctx["facets"] = facets
        ctx["search_value"] = getattr(self, "_search", "")
        return ctx


class DonorDetailView(GrantsStaffMixin, DetailView):
    model = Donor
    template_name = "operations/donor_detail.html"
    context_object_name = "donor"


class DonorCreateView(GrantsStaffMixin, CreateView):
    model = Donor
    form_class = DonorForm
    template_name = "operations/donor_form.html"

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["confirm_duplicate"] = self.request.POST.get("confirm_duplicate") == "1"
        return kwargs

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.created_by = self.request.user
        self.object.save()
        messages.success(self.request, "Donor added.")
        return redirect("rims_operations:donor_detail", pk=self.object.pk)


class DonorUpdateView(GrantsStaffMixin, UpdateView):
    model = Donor
    form_class = DonorForm
    template_name = "operations/donor_edit.html"

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        # Allow editing without re-tripping the duplicate detector against itself.
        kwargs["confirm_duplicate"] = True
        return kwargs

    def form_valid(self, form):
        self.object = form.save()
        messages.success(self.request, "Donor updated.")
        return redirect("rims_operations:donor_detail", pk=self.object.pk)


# ---------------------------------------------------------------------------
# PRD §5.1 FRFA-GPS006 — Strategic Objective admin.
# ---------------------------------------------------------------------------
class StrategicObjectiveListView(GrantsStaffMixin, SortableListMixin, CsvExportMixin, ListView):
    model = StrategicObjective
    template_name = "operations/strategic_objective_list.html"
    context_object_name = "objectives"
    paginate_by = 25

    sortable_fields = {
        "name": "name",
        "active": "active",
        "created": "created_at",
    }
    default_sort = "name"

    csv_columns = (
        ("Name", "name"),
        ("Slug", "slug"),
        ("Active", lambda o: "Yes" if o.active else "No"),
        ("Description", "description"),
        ("Created", lambda o: o.created_at.isoformat() if o.created_at else ""),
    )
    csv_filename = "strategic_objectives"

    def get_queryset(self):
        qs = StrategicObjective.objects.all()
        self._search = (self.request.GET.get("q") or "").strip()
        active = (self.request.GET.get("active") or "").strip()
        self._active_filter = active
        if self._search:
            qs = qs.filter(Q(name__icontains=self._search) | Q(slug__icontains=self._search))
        if active in ("1", "true", "yes"):
            qs = qs.filter(active=True)
        elif active in ("0", "false", "no"):
            qs = qs.filter(active=False)
        return self.apply_sort(qs)

    def get_template_names(self):
        if getattr(self.request, "htmx", False):
            return ["operations/partials/strategic_objective_list_results.html"]
        return super().get_template_names()

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        base_qs = StrategicObjective.objects.all()
        total = base_qs.count()
        n_active = base_qs.filter(active=True).count()
        active = getattr(self, "_active_filter", "")
        facets = [
            {"label": "All", "href": self.request.path, "count": total, "active": active == ""},
            {"label": "Active", "href": f"{self.request.path}?active=1", "count": n_active, "active": active in ("1", "true", "yes")},
            {"label": "Inactive", "href": f"{self.request.path}?active=0", "count": total - n_active, "active": active in ("0", "false", "no")},
        ]
        ctx["facets"] = facets
        ctx["search_value"] = getattr(self, "_search", "")
        return ctx


class StrategicObjectiveCreateView(GrantsStaffMixin, CreateView):
    model = StrategicObjective
    form_class = StrategicObjectiveForm
    template_name = "operations/strategic_objective_form.html"

    def form_valid(self, form):
        self.object = form.save()
        messages.success(self.request, "Strategic objective added.")
        return redirect("rims_operations:strategic_objective_list")


class StrategicObjectiveUpdateView(GrantsStaffMixin, UpdateView):
    model = StrategicObjective
    form_class = StrategicObjectiveForm
    template_name = "operations/strategic_objective_edit.html"

    def form_valid(self, form):
        self.object = form.save()
        messages.success(self.request, "Strategic objective updated.")
        return redirect("rims_operations:strategic_objective_list")


# ---------------------------------------------------------------------------
# PRD NFRFA011 — Audit log viewer (read-only, filterable).
# ---------------------------------------------------------------------------
class AuditLogView(AuditorReadOnlyMixin, SortableListMixin, CsvExportMixin, ListView):
    model = AuditLog
    template_name = "operations/audit_log.html"
    context_object_name = "rows"
    paginate_by = 25

    sortable_fields = {
        "when": "timestamp",
        "actor": "actor__email",
        "action": "action",
        "target": "target_model",
    }
    default_sort = "-timestamp"

    csv_columns = (
        ("Timestamp", lambda r: r.timestamp.isoformat() if r.timestamp else ""),
        ("Actor", lambda r: r.actor.email if r.actor_id else ""),
        ("Action", "action"),
        ("Target app", "target_app"),
        ("Target model", "target_model"),
        ("Object", "object_repr"),
        ("Changes", lambda r: str(r.changes) if r.changes else ""),
    )
    csv_filename = "audit_log"

    def get_queryset(self):
        qs = AuditLog.objects.select_related("actor")
        params = self.request.GET
        if params.get("actor"):
            qs = qs.filter(actor__email__icontains=params["actor"])
        if params.get("action"):
            qs = qs.filter(action__icontains=params["action"])
        if params.get("target_app"):
            qs = qs.filter(target_app=params["target_app"])
        if params.get("target_model"):
            qs = qs.filter(target_model__icontains=params["target_model"])
        if params.get("date_from"):
            qs = qs.filter(timestamp__gte=params["date_from"])
        if params.get("date_to"):
            qs = qs.filter(timestamp__lte=params["date_to"])
        return self.apply_sort(qs)

    def get_context_data(self, **kwargs):
        from apps.rims.grants.ai_helpers import audit_anomaly_detector

        ctx = super().get_context_data(**kwargs)
        ctx["filter_values"] = {
            k: self.request.GET.get(k, "")
            for k in ("actor", "action", "target_app", "target_model", "date_from", "date_to")
        }
        ctx["ai_audit_watchdog"] = audit_anomaly_detector(ctx.get("rows", []))
        return ctx


class ArchivedAwardsListView(AuditorReadOnlyMixin, SortableListMixin, CsvExportMixin, ListView):
    """PRD §5.1 FRFA-CO015/016/017 — auditor-only archive of closed awards."""

    template_name = "operations/archived_awards.html"
    context_object_name = "awards"
    paginate_by = 25

    sortable_fields = {
        "archived": "archived_at",
        "closed": "closed_at",
        "call": "application__call__title",
        "applicant": "application__applicant__email",
    }
    default_sort = "-archived_at"

    csv_columns = (
        ("Award ID", "pk"),
        ("Call", lambda a: a.application.call.title if a.application_id else ""),
        ("Applicant", lambda a: a.application.applicant.email if a.application_id else ""),
        ("Currency", "currency"),
        ("Award amount", lambda a: str(a.amount)),
        ("Closed at", lambda a: a.closed_at.isoformat() if a.closed_at else ""),
        ("Archived at", lambda a: a.archived_at.isoformat() if a.archived_at else ""),
        (
            "Residual returned",
            lambda a: (a.closeout_record.financial_snapshot or {}).get("residual_returned", "")
            if hasattr(a, "closeout_record") and getattr(a, "closeout_record", None)
            else "",
        ),
    )
    csv_filename = "archived_awards"

    def get_queryset(self):
        from apps.rims.grants.models import Award

        qs = (
            Award.objects.filter(archived_at__isnull=False)
            .select_related("application__call", "application__applicant", "closeout_record")
        )
        return self.apply_sort(qs)


class MemberUniversityListView(GrantsStaffMixin, SortableListMixin, ListView):
    model = MemberUniversity
    template_name = "operations/member_university_list.html"
    context_object_name = "member_universities"
    paginate_by = 20

    sortable_fields = {
        "name": "institution__name",
        "country": "institution__country",
        "status": "membership_status",
    }
    default_sort = "name"

    def get_queryset(self):
        qs = MemberUniversity.objects.select_related("institution")
        q = (self.request.GET.get("q") or "").strip()
        if q:
            qs = qs.filter(
                Q(institution__name__icontains=q)
                | Q(focal_person_name__icontains=q)
                | Q(membership_number__icontains=q)
            )
        status = self.request.GET.get("status")
        if status:
            qs = qs.filter(membership_status=status)
        return self.apply_sort(qs)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["status_choices"] = MemberUniversity.MembershipStatus.choices
        return ctx

    def get_template_names(self):
        if getattr(self.request, "htmx", False):
            return ["operations/partials/member_university_list_results.html"]
        return super().get_template_names()


class MemberUniversityDetailView(GrantsStaffMixin, DetailView):
    model = MemberUniversity
    template_name = "operations/member_university_detail.html"
    context_object_name = "member_university"


class MemberUniversityCreateView(GrantsStaffMixin, CreateView):
    model = MemberUniversity
    fields = [
        "institution", "membership_status", "date_admitted", "membership_number",
        "vice_chancellor", "focal_person_name", "focal_person_email", "focal_person_phone",
        "postgraduate_programmes", "research_focus_areas", "annual_fee_paid", "annual_fee_year", "notes",
    ]
    template_name = "operations/member_university_form.html"

    def get_success_url(self):
        from django.urls import reverse
        return reverse("rims_operations:member_university_detail", kwargs={"pk": self.object.pk})


class MemberUniversityUpdateView(GrantsStaffMixin, UpdateView):
    model = MemberUniversity
    fields = [
        "membership_status", "date_admitted", "membership_number",
        "vice_chancellor", "focal_person_name", "focal_person_email", "focal_person_phone",
        "postgraduate_programmes", "research_focus_areas", "annual_fee_paid", "annual_fee_year", "notes",
    ]
    template_name = "operations/member_university_edit.html"

    def get_success_url(self):
        from django.urls import reverse
        return reverse("rims_operations:member_university_detail", kwargs={"pk": self.object.pk})
