"""SP5 investment views — investors, funding calls, applications, matches,
performance dashboard, investor pitch flow, manual funding.

Side-effects flow through ``services``; the view layer does form binding,
permission checks, and template / HTMX response selection only.
"""
from __future__ import annotations

from decimal import Decimal

from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import models
from django.db.models import Count, Q, Sum
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils import timezone
from django.views import View
from django.views.decorators.http import require_POST
from django.views.generic import (
    CreateView,
    DetailView,
    ListView,
    TemplateView,
    UpdateView,
)

from apps.core.lists import paginate_qs
from apps.core.permissions.roles import UserRole
from apps.smehub._shared.role_gates import (
    EntrepreneurRequired,
    EntrepreneurVerifiedRequired,
    InvestorRequired,
    SMEAdminRequired,
)
from apps.smehub.investment import services
from apps.smehub.investment.forms import (
    FundingApplicationDecisionForm,
    FundingApplicationDocumentFormSet,
    FundingApplicationForm,
    FundingApplicationStartForm,
    FundingCallForm,
    InvestorConnectionRequestForm,
    InvestorDeactivateForm,
    InvestorForm,
    InvestorRejectForm,
    InvestorRequestDeclineForm,
    InvestorSelfRegisterForm,
    ManualFundingRecordForm,
    ReadinessThresholdConfigForm,
    ShareDashboardForm,
)
from apps.smehub.investment.models import (
    DashboardAccessLog,
    DashboardViewEvent,
    FundingApplication,
    FundingCall,
    InvestmentReadiness,
    Investor,
    InvestorConnectionRequest,
    InvestorMatch,
    ManualFundingRecord,
    ReadinessThresholdConfig,
    SMEPerformanceSnapshot,
)
from apps.smehub.onboarding.models import EntrepreneurProfile

SME_ADMIN_ROLES = {
    UserRole.SYSTEM_ADMIN,
    UserRole.ADMIN,
    UserRole.SME_ADMIN,
}


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _is_sme_admin(user) -> bool:
    if not user.is_authenticated:
        return False
    if user.is_superuser:
        return True
    return user.role in SME_ADMIN_ROLES


def _entrepreneur_profile(user) -> EntrepreneurProfile | None:
    if not user.is_authenticated:
        return None
    return EntrepreneurProfile.objects.filter(user=user).first()


def _investor_for_user(user) -> Investor | None:
    if not user.is_authenticated:
        return None
    return Investor.objects.filter(contact_user=user).first()


def _investor_can_decide_request(user, request_obj: InvestorConnectionRequest) -> bool:
    if _is_sme_admin(user):
        return True
    investor = request_obj.investor
    return investor.contact_user_id == user.pk if investor else False


# ---------------------------------------------------------------------------
# Hub landing — Slice B.2.
# ---------------------------------------------------------------------------

class InvestmentHubView(LoginRequiredMixin, TemplateView):
    """Hub re-homing Funding calls / Investor directory / Matches / My apps / Pitch requests."""

    template_name = "smehub/investment/hub.html"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        user = self.request.user
        entrepreneur = _entrepreneur_profile(user) if user.is_authenticated else None
        investor = _investor_for_user(user) if user.is_authenticated else None
        ctx["entrepreneur"] = entrepreneur
        ctx["investor"] = investor
        ctx["is_admin"] = _is_sme_admin(user)
        ctx["open_calls"] = (
            FundingCall.objects.filter(status=FundingCall.Status.PUBLISHED)
            .select_related("investor")
            .order_by("application_deadline")[:6]
        )
        ctx["open_calls_count"] = FundingCall.objects.filter(
            status=FundingCall.Status.PUBLISHED,
        ).count()
        ctx["verified_investor_count"] = Investor.objects.filter(
            verification_status=Investor.Status.VERIFIED,
        ).count()
        if entrepreneur:
            ctx["my_application_count"] = FundingApplication.objects.filter(
                entrepreneur=entrepreneur,
            ).count()
            ctx["my_pitch_count"] = InvestorConnectionRequest.objects.filter(
                entrepreneur=entrepreneur,
            ).count()
        return ctx


# ---------------------------------------------------------------------------
# Admin dashboard
# ---------------------------------------------------------------------------

class ReadinessThresholdConfigView(SMEAdminRequired, LoginRequiredMixin, View):
    """FRSME-INV010 — edit the active investment-readiness thresholds."""

    template_name = "smehub/investment/readiness_thresholds.html"

    def _active_config(self):
        return (
            ReadinessThresholdConfig.objects.filter(is_active=True)
            .order_by("-updated_at")
            .first()
        )

    def get(self, request):
        form = ReadinessThresholdConfigForm(instance=self._active_config())
        return render(request, self.template_name, {"form": form, "config": self._active_config()})

    def post(self, request):
        config = self._active_config()
        form = ReadinessThresholdConfigForm(request.POST, instance=config)
        if not form.is_valid():
            return render(request, self.template_name, {"form": form, "config": config}, status=400)
        obj = form.save(commit=False)
        obj.is_active = True
        obj.updated_by = request.user
        obj.save()
        messages.success(request, "Readiness thresholds updated.")
        return redirect("smehub_investment:readiness_thresholds")


class InvestmentAdminDashboardView(SMEAdminRequired, LoginRequiredMixin, TemplateView):
    template_name = "smehub/investment/admin_dashboard.html"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["investor_counts"] = {
            "total": Investor.objects.count(),
            "verified": Investor.objects.filter(verification_status=Investor.Status.VERIFIED).count(),
            "pending": Investor.objects.filter(verification_status=Investor.Status.PENDING).count(),
            "deactivated": Investor.objects.filter(verification_status=Investor.Status.DEACTIVATED).count(),
        }
        ctx["call_counts"] = {
            "total": FundingCall.objects.count(),
            "draft": FundingCall.objects.filter(status=FundingCall.Status.DRAFT).count(),
            "published": FundingCall.objects.filter(status=FundingCall.Status.PUBLISHED).count(),
            "closed": FundingCall.objects.filter(status=FundingCall.Status.CLOSED).count(),
        }
        status_labels = dict(FundingApplication.Status.choices)
        ctx["application_counts"] = [
            {
                "status": row["status"],
                "label": status_labels.get(row["status"], row["status"]),
                "count": row["count"],
            }
            for row in (
                FundingApplication.objects.values("status")
                .annotate(count=Count("id"))
                .order_by("status")
            )
        ]
        ctx["request_counts"] = {
            "pending": InvestorConnectionRequest.objects.filter(
                status=InvestorConnectionRequest.Status.PENDING,
            ).count(),
            "accepted": InvestorConnectionRequest.objects.filter(
                status=InvestorConnectionRequest.Status.ACCEPTED,
            ).count(),
        }
        manual_qs = ManualFundingRecord.objects.aggregate(total=Sum("amount"))
        ctx["manual_total"] = manual_qs.get("total") or Decimal("0")
        ctx["recent_calls"] = FundingCall.objects.order_by("-created_at")[:6]
        ctx["pending_investors"] = Investor.objects.filter(
            verification_status=Investor.Status.PENDING,
        ).order_by("-created_at")[:8]
        ctx["recent_applications"] = (
            FundingApplication.objects.select_related("funding_call", "business")
            .order_by("-updated_at")[:8]
        )
        return ctx


# ---------------------------------------------------------------------------
# Investor directory
# ---------------------------------------------------------------------------

class InvestorDirectoryView(LoginRequiredMixin, ListView):
    template_name = "smehub/investment/investor_directory.html"
    context_object_name = "investors"
    paginate_by = 12

    def get_template_names(self):
        if self.request.htmx:
            return ["smehub/investment/partials/investor_directory_results.html"]
        return [self.template_name]

    def get_queryset(self):
        qs = Investor.objects.all()
        if not _is_sme_admin(self.request.user):
            qs = qs.filter(verification_status=Investor.Status.VERIFIED)
        type_filter = self.request.GET.get("type")
        if type_filter:
            qs = qs.filter(type=type_filter)
        sector = self.request.GET.get("sector")
        if sector:
            qs = qs.filter(investment_focus__icontains=sector)
        country = self.request.GET.get("country")
        if country:
            qs = qs.filter(geographic_preference__icontains=country)
        return qs.order_by("org_name")

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["type_choices"] = Investor.Type.choices
        ctx["type_filter"] = self.request.GET.get("type", "")
        ctx["sector_filter"] = self.request.GET.get("sector", "")
        ctx["country_filter"] = self.request.GET.get("country", "")
        ctx["is_admin"] = _is_sme_admin(self.request.user)
        verified_qs = Investor.objects.filter(verification_status=Investor.Status.VERIFIED)
        ctx["stat_counts"] = {
            "verified": verified_qs.count(),
            "pending": Investor.objects.filter(
                verification_status=Investor.Status.PENDING,
            ).count(),
            "with_ticket_disclosed": verified_qs.filter(
                ticket_size_min__isnull=False,
                ticket_size_max__isnull=False,
            ).count(),
            "type_diversity": verified_qs.values("type").distinct().count(),
        }
        return ctx


class InvestorDetailView(LoginRequiredMixin, DetailView):
    model = Investor
    template_name = "smehub/investment/investor_detail.html"
    context_object_name = "investor"

    def dispatch(self, request, *args, **kwargs):
        # FRSME-INV003 — only admins, the investor themselves, or other
        # authenticated users viewing a VERIFIED investor may see this page.
        # PENDING / REJECTED / DEACTIVATED investors are private.
        investor = self.get_object()
        is_admin = _is_sme_admin(request.user)
        is_self = investor.contact_user_id == request.user.pk
        if not (is_admin or is_self or investor.verification_status == Investor.Status.VERIFIED):
            raise PermissionDenied("This investor profile is not available.")
        self.object = investor
        return super().dispatch(request, *args, **kwargs)

    def get_object(self, queryset=None):
        if getattr(self, "object", None) is not None:
            return self.object
        return super().get_object(queryset)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        investor: Investor = ctx["investor"]
        ctx["is_admin"] = _is_sme_admin(self.request.user)
        ctx["is_self"] = investor.contact_user_id == self.request.user.pk
        ctx["entrepreneur"] = _entrepreneur_profile(self.request.user)
        ctx["funding_calls"] = investor.funding_calls.filter(
            status__in=[FundingCall.Status.PUBLISHED, FundingCall.Status.CLOSED],
        ).order_by("-published_at")[:6]
        requests_qs = investor.connection_requests.all()
        ctx["request_count"] = requests_qs.count()
        ctx["request_breakdown"] = {
            "pending": requests_qs.filter(
                status=InvestorConnectionRequest.Status.PENDING,
            ).count(),
            "accepted": requests_qs.filter(
                status=InvestorConnectionRequest.Status.ACCEPTED,
            ).count(),
            "declined": requests_qs.filter(
                status=InvestorConnectionRequest.Status.DECLINED,
            ).count(),
        }
        ctx["funding_calls_total"] = investor.funding_calls.count()
        ctx["manual_funding_count"] = investor.manual_funding_records.count()
        if ctx["is_admin"] or ctx["is_self"]:
            ctx["recent_accepted_requests"] = (
                requests_qs.filter(status=InvestorConnectionRequest.Status.ACCEPTED)
                .select_related("entrepreneur__user", "business", "deal_room")
                .order_by("-decided_at")[:5]
            )
        return ctx


class InvestorCreateView(SMEAdminRequired, LoginRequiredMixin, CreateView):
    model = Investor
    form_class = InvestorForm
    template_name = "smehub/investment/investor_form.html"

    def form_valid(self, form):
        cleaned = form.cleaned_data
        investor = services.create_investor(
            by_user=self.request.user,
            org_name=cleaned["org_name"],
            type=cleaned["type"],
            description=cleaned.get("description", ""),
            ticket_size_min=cleaned.get("ticket_size_min"),
            ticket_size_max=cleaned.get("ticket_size_max"),
            contact_email=cleaned.get("contact_email", ""),
            contact_phone=cleaned.get("contact_phone", ""),
            website=cleaned.get("website", ""),
            logo=cleaned.get("logo"),
            investment_focus=cleaned.get("investment_focus", []),
            geographic_preference=cleaned.get("geographic_preference", []),
            auto_verify=True,
        )
        messages.success(self.request, f"Investor profile “{investor.org_name}” created.")
        return redirect("smehub_investment:investor_detail", pk=investor.pk)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["form_title"] = "New investor"
        ctx["submit_label"] = "Create investor"
        return ctx


class InvestorUpdateView(LoginRequiredMixin, UpdateView):
    model = Investor
    form_class = InvestorForm
    template_name = "smehub/investment/investor_form.html"

    def dispatch(self, request, *args, **kwargs):
        investor = self.get_object()
        if not (_is_sme_admin(request.user) or investor.contact_user_id == request.user.pk):
            raise PermissionDenied("You cannot edit this investor profile.")
        return super().dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        services.update_investor(
            self.object,
            by_user=self.request.user,
            **form.cleaned_data,
        )
        messages.success(self.request, "Investor profile updated.")
        return redirect("smehub_investment:investor_detail", pk=self.object.pk)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["form_title"] = f"Edit {self.object.org_name}"
        ctx["submit_label"] = "Save changes"
        return ctx


class InvestorSelfRegisterView(InvestorRequired, LoginRequiredMixin, View):
    template_name = "smehub/investment/investor_self_register.html"

    def get(self, request):
        existing = _investor_for_user(request.user)
        if existing:
            messages.info(request, "You already have an investor profile.")
            return redirect("smehub_investment:investor_detail", pk=existing.pk)
        form = InvestorSelfRegisterForm()
        return render(request, self.template_name, {"form": form})

    def post(self, request):
        form = InvestorSelfRegisterForm(request.POST, request.FILES)
        if not form.is_valid():
            return render(request, self.template_name, {"form": form})
        cleaned = form.cleaned_data
        investor = services.register_investor_self(
            by_user=request.user,
            org_name=cleaned["org_name"],
            type=cleaned["type"],
            description=cleaned.get("description", ""),
            ticket_size_min=cleaned.get("ticket_size_min"),
            ticket_size_max=cleaned.get("ticket_size_max"),
            contact_email=cleaned.get("contact_email", "") or request.user.email,
            contact_phone=cleaned.get("contact_phone", ""),
            website=cleaned.get("website", ""),
            logo=cleaned.get("logo"),
            investment_focus=cleaned.get("investment_focus", []),
            geographic_preference=cleaned.get("geographic_preference", []),
        )
        messages.success(request, "Profile submitted. An admin will review it shortly.")
        return redirect("smehub_investment:investor_detail", pk=investor.pk)


class InvestorAdminQueueView(SMEAdminRequired, LoginRequiredMixin, ListView):
    template_name = "smehub/investment/investor_admin_queue.html"
    context_object_name = "investors"
    paginate_by = 20

    def get_queryset(self):
        return Investor.objects.exclude(
            verification_status=Investor.Status.REJECTED,
        ).order_by("verification_status", "-created_at")

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["pending"] = paginate_qs(
            self.request,
            Investor.objects.filter(
                verification_status=Investor.Status.PENDING,
            ).order_by("-created_at"),
            param="pending_page",
        )
        ctx["verified"] = paginate_qs(
            self.request,
            Investor.objects.filter(
                verification_status=Investor.Status.VERIFIED,
            ).order_by("org_name"),
            param="verified_page",
        )
        ctx["deactivated"] = paginate_qs(
            self.request,
            Investor.objects.filter(
                verification_status=Investor.Status.DEACTIVATED,
            ).order_by("-updated_at"),
            param="deactivated_page",
        )
        ctx["rejected"] = paginate_qs(
            self.request,
            Investor.objects.filter(
                verification_status=Investor.Status.REJECTED,
            ).order_by("-updated_at"),
            param="rejected_page",
        )
        return ctx


class InvestorAdminVerifyView(SMEAdminRequired, LoginRequiredMixin, DetailView):
    model = Investor
    template_name = "smehub/investment/investor_admin_verify.html"
    context_object_name = "investor"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["reject_form"] = InvestorRejectForm()
        ctx["deactivate_form"] = InvestorDeactivateForm()
        return ctx


@require_POST
def investor_verify_view(request, pk):
    if not _is_sme_admin(request.user):
        raise PermissionDenied()
    investor = get_object_or_404(Investor, pk=pk)
    services.verify_investor(investor, by_user=request.user)
    messages.success(request, f"Verified {investor.org_name}.")
    return redirect("smehub_investment:investor_admin_verify", pk=investor.pk)


@require_POST
def investor_reject_view(request, pk):
    if not _is_sme_admin(request.user):
        raise PermissionDenied()
    investor = get_object_or_404(Investor, pk=pk)
    form = InvestorRejectForm(request.POST)
    if not form.is_valid():
        messages.error(request, "Reason is required to reject.")
        return redirect("smehub_investment:investor_admin_verify", pk=investor.pk)
    services.reject_investor(investor, by_user=request.user, reason=form.cleaned_data["reason"])
    messages.success(request, f"Rejected {investor.org_name}.")
    return redirect("smehub_investment:investor_admin_queue")


@require_POST
def investor_deactivate_view(request, pk):
    if not _is_sme_admin(request.user):
        raise PermissionDenied()
    investor = get_object_or_404(Investor, pk=pk)
    form = InvestorDeactivateForm(request.POST)
    reason = form.cleaned_data.get("reason", "") if form.is_valid() else ""
    services.deactivate_investor(investor, by_user=request.user, reason=reason)
    messages.success(request, f"Deactivated {investor.org_name}.")
    return redirect("smehub_investment:investor_admin_queue")


@require_POST
def investor_reactivate_view(request, pk):
    if not _is_sme_admin(request.user):
        raise PermissionDenied()
    investor = get_object_or_404(Investor, pk=pk)
    services.reactivate_investor(investor, by_user=request.user)
    messages.success(request, f"Reactivated {investor.org_name}.")
    return redirect("smehub_investment:investor_admin_queue")


# ---------------------------------------------------------------------------
# Funding calls
# ---------------------------------------------------------------------------

class FundingCallListView(LoginRequiredMixin, ListView):
    template_name = "smehub/investment/funding_calls.html"
    context_object_name = "funding_calls"
    paginate_by = 12

    def get_queryset(self):
        qs = FundingCall.objects.select_related("investor")
        if not _is_sme_admin(self.request.user):
            qs = qs.exclude(status=FundingCall.Status.DRAFT)
        type_filter = self.request.GET.get("type")
        if type_filter:
            qs = qs.filter(type=type_filter)
        status_filter = self.request.GET.get("status")
        if status_filter:
            qs = qs.filter(status=status_filter)
        return qs.order_by("status", "-application_deadline")

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["type_choices"] = FundingCall.Type.choices
        ctx["status_choices"] = FundingCall.Status.choices
        ctx["type_filter"] = self.request.GET.get("type", "")
        ctx["status_filter"] = self.request.GET.get("status", "")
        ctx["is_admin"] = _is_sme_admin(self.request.user)
        ctx["entrepreneur"] = _entrepreneur_profile(self.request.user)
        ctx["stat_counts"] = {
            "open": FundingCall.objects.filter(status=FundingCall.Status.PUBLISHED).count(),
            "closed": FundingCall.objects.filter(status=FundingCall.Status.CLOSED).count(),
            "applications": FundingApplication.objects.count(),
            "accepted": FundingApplication.objects.filter(
                status=FundingApplication.Status.ACCEPTED,
            ).count(),
        }
        return ctx


class FundingCallManageView(SMEAdminRequired, LoginRequiredMixin, ListView):
    template_name = "smehub/investment/funding_call_manage.html"
    context_object_name = "funding_calls"
    paginate_by = 25

    def get_queryset(self):
        return FundingCall.objects.select_related("investor").order_by(
            "status", "-application_deadline",
        )


class FundingCallDetailView(LoginRequiredMixin, DetailView):
    model = FundingCall
    template_name = "smehub/investment/funding_call_detail.html"
    context_object_name = "funding_call"

    def get_queryset(self):
        qs = FundingCall.objects.select_related("investor")
        if not _is_sme_admin(self.request.user):
            qs = qs.exclude(status=FundingCall.Status.DRAFT)
        return qs

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        call: FundingCall = ctx["funding_call"]
        ctx["is_admin"] = _is_sme_admin(self.request.user)
        entrepreneur = _entrepreneur_profile(self.request.user)
        ctx["entrepreneur"] = entrepreneur
        apps_qs = call.applications.all()
        ctx["application_count"] = apps_qs.count()
        ctx["confirmed_count"] = apps_qs.filter(
            status=FundingApplication.Status.ACCEPTED,
        ).count()
        ctx["status_breakdown"] = [
            {
                "key": s,
                "label": label,
                "count": apps_qs.filter(status=s).count(),
            }
            for s, label in FundingApplication.Status.choices
        ]
        # Deadline countdown
        if call.application_deadline:
            delta = call.application_deadline - timezone.now()
            ctx["days_remaining"] = max(delta.days, 0)
            ctx["is_past_deadline"] = delta.total_seconds() < 0
        ctx["my_application"] = None
        if entrepreneur:
            ctx["my_application"] = apps_qs.filter(
                entrepreneur=entrepreneur,
            ).first()
        # Recent applicants for admins
        if ctx["is_admin"]:
            ctx["recent_applications"] = (
                apps_qs.select_related("entrepreneur__user", "business")
                .order_by("-updated_at")[:5]
            )
        return ctx


_FUNDING_CALL_WIZARD_STEPS = [
    {"n": 1, "title": "Title & type"},
    {"n": 2, "title": "What's on offer"},
    {"n": 3, "title": "Eligibility"},
]


class FundingCallCreateView(SMEAdminRequired, LoginRequiredMixin, CreateView):
    model = FundingCall
    form_class = FundingCallForm
    template_name = "smehub/investment/funding_call_form.html"

    def form_valid(self, form):
        cleaned = form.cleaned_data
        call = services.create_funding_call(
            by_user=self.request.user,
            title=cleaned["title"],
            type=cleaned["type"],
            application_deadline=cleaned["application_deadline"],
            summary=cleaned.get("summary", ""),
            description=cleaned.get("description", ""),
            target_sectors=cleaned.get("target_sectors", []),
            target_stages=cleaned.get("target_stages", []),
            target_countries=cleaned.get("target_countries", []),
            amount_min=cleaned.get("amount_min"),
            amount_max=cleaned.get("amount_max"),
            contact_email=cleaned.get("contact_email", ""),
            cover_image=cleaned.get("cover_image"),
            investor=cleaned.get("investor"),
            min_full_time_staff=cleaned.get("min_full_time_staff"),
            require_incubation_complete=cleaned.get("require_incubation_complete", False),
        )
        messages.success(self.request, f"Funding call “{call.title}” created in draft.")
        return redirect("smehub_investment:funding_call_detail", pk=call.pk)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["form_title"] = "New funding call"
        ctx["submit_label"] = "Create funding call"
        ctx["wizard_steps"] = _FUNDING_CALL_WIZARD_STEPS
        ctx["cancel_url"] = reverse("smehub_investment:funding_calls")
        return ctx


class FundingCallUpdateView(SMEAdminRequired, LoginRequiredMixin, UpdateView):
    model = FundingCall
    form_class = FundingCallForm
    template_name = "smehub/investment/funding_call_form.html"

    def form_valid(self, form):
        call = form.save()
        # Re-write JSON list fields (handled by ModelForm.save now via __init__).
        call.target_sectors = list(form.cleaned_data.get("target_sectors", []))
        call.target_stages = list(form.cleaned_data.get("target_stages", []))
        call.target_countries = list(form.cleaned_data.get("target_countries", []))
        call.save(update_fields=[
            "target_sectors", "target_stages", "target_countries", "updated_at",
        ])
        messages.success(self.request, "Funding call updated.")
        return redirect("smehub_investment:funding_call_detail", pk=call.pk)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["form_title"] = f"Edit {self.object.title}"
        ctx["submit_label"] = "Save changes"
        ctx["wizard_steps"] = _FUNDING_CALL_WIZARD_STEPS
        ctx["cancel_url"] = reverse("smehub_investment:funding_calls")
        return ctx


@require_POST
def funding_call_publish_view(request, pk):
    if not _is_sme_admin(request.user):
        raise PermissionDenied()
    call = get_object_or_404(FundingCall, pk=pk)
    try:
        services.publish_funding_call(call, by_user=request.user)
        messages.success(request, f"Published “{call.title}”.")
    except services.InvestmentStateError as exc:
        messages.error(request, str(exc))
    return redirect("smehub_investment:funding_call_detail", pk=call.pk)


@require_POST
def funding_call_close_view(request, pk):
    if not _is_sme_admin(request.user):
        raise PermissionDenied()
    call = get_object_or_404(FundingCall, pk=pk)
    try:
        services.close_funding_call(call, by_user=request.user)
        messages.success(request, f"Closed “{call.title}”.")
    except services.InvestmentStateError as exc:
        messages.error(request, str(exc))
    return redirect("smehub_investment:funding_call_detail", pk=call.pk)


class FundingCallApplicationsView(SMEAdminRequired, LoginRequiredMixin, DetailView):
    model = FundingCall
    template_name = "smehub/investment/funding_call_applications.html"
    context_object_name = "funding_call"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        call: FundingCall = ctx["funding_call"]
        ctx["applications"] = paginate_qs(
            self.request,
            call.applications.select_related("entrepreneur__user", "business")
            .order_by("status", "-submitted_at"),
            per_page=25,
            param="app_page",
        )
        ctx["decision_form"] = FundingApplicationDecisionForm()
        return ctx


# ---------------------------------------------------------------------------
# Funding applications
# ---------------------------------------------------------------------------

class FundingApplicationStartView(EntrepreneurRequired, LoginRequiredMixin, View):
    template_name = "smehub/investment/funding_application_start.html"

    def get(self, request, pk):
        call = get_object_or_404(FundingCall, pk=pk)
        entrepreneur = _entrepreneur_profile(request.user)
        if entrepreneur is None:
            raise PermissionDenied("Entrepreneur profile required.")
        form = FundingApplicationStartForm(entrepreneur=entrepreneur)
        return render(
            request,
            self.template_name,
            {"funding_call": call, "form": form, "entrepreneur": entrepreneur},
        )

    def post(self, request, pk):
        call = get_object_or_404(FundingCall, pk=pk)
        entrepreneur = _entrepreneur_profile(request.user)
        if entrepreneur is None:
            raise PermissionDenied("Entrepreneur profile required.")
        form = FundingApplicationStartForm(request.POST, entrepreneur=entrepreneur)
        if not form.is_valid():
            return render(
                request,
                self.template_name,
                {"funding_call": call, "form": form, "entrepreneur": entrepreneur},
            )
        try:
            application = services.start_funding_application(
                funding_call=call,
                entrepreneur=entrepreneur,
                business=form.cleaned_data["business"],
            )
        except services.InvestmentStateError as exc:
            messages.error(request, str(exc))
            return redirect("smehub_investment:funding_call_detail", pk=call.pk)
        messages.success(request, "Application draft started — fill it in and submit when ready.")
        return redirect("smehub_investment:funding_application_edit", pk=application.pk)


class FundingApplicationDetailView(LoginRequiredMixin, DetailView):
    model = FundingApplication
    template_name = "smehub/investment/funding_application_detail.html"
    context_object_name = "application"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        application: FundingApplication = ctx["application"]
        ctx["is_admin"] = _is_sme_admin(self.request.user)
        ctx["is_owner"] = application.entrepreneur.user_id == self.request.user.pk
        if not (ctx["is_admin"] or ctx["is_owner"]):
            raise PermissionDenied()
        ctx["decision_form"] = FundingApplicationDecisionForm()
        ctx["supporting_documents"] = application.supporting_documents.all()
        return ctx


class FundingApplicationEditView(EntrepreneurRequired, LoginRequiredMixin, View):
    template_name = "smehub/investment/funding_application_form.html"

    def _get_application(self, request, pk):
        application = get_object_or_404(FundingApplication, pk=pk)
        if application.entrepreneur.user_id != request.user.pk:
            raise PermissionDenied()
        if application.status not in (
            FundingApplication.Status.DRAFT,
            FundingApplication.Status.RETURNED_FOR_INFO,
        ):
            raise PermissionDenied("Application is locked from edits.")
        return application

    def get(self, request, pk):
        application = self._get_application(request, pk)
        form = FundingApplicationForm(instance=application)
        formset = FundingApplicationDocumentFormSet(instance=application)
        return render(request, self.template_name, {
            "application": application,
            "form": form,
            "formset": formset,
        })

    def post(self, request, pk):
        application = self._get_application(request, pk)
        form = FundingApplicationForm(request.POST, request.FILES, instance=application)
        formset = FundingApplicationDocumentFormSet(
            request.POST, request.FILES, instance=application,
        )
        if form.is_valid() and formset.is_valid():
            form.save()
            formset.save()
            messages.success(request, "Saved.")
            return redirect("smehub_investment:funding_application_edit", pk=application.pk)
        return render(request, self.template_name, {
            "application": application,
            "form": form,
            "formset": formset,
        })


@require_POST
def funding_application_autosave_view(request, pk):
    application = get_object_or_404(FundingApplication, pk=pk)
    if application.entrepreneur.user_id != request.user.pk:
        raise PermissionDenied()
    payload = {
        key: value for key, value in request.POST.items() if key != "csrfmiddlewaretoken"
    }
    services.auto_save_funding_application(application, payload=payload)
    return HttpResponse(status=204)


@require_POST
def funding_application_submit_view(request, pk):
    application = get_object_or_404(FundingApplication, pk=pk)
    if application.entrepreneur.user_id != request.user.pk:
        raise PermissionDenied()
    try:
        services.submit_funding_application(application, by_user=request.user)
        messages.success(request, "Application submitted.")
    except services.InvestmentStateError as exc:
        messages.error(request, str(exc))
    return redirect("smehub_investment:funding_application_detail", pk=application.pk)


@require_POST
def funding_application_withdraw_view(request, pk):
    application = get_object_or_404(FundingApplication, pk=pk)
    if application.entrepreneur.user_id != request.user.pk and not _is_sme_admin(request.user):
        raise PermissionDenied()
    try:
        application.withdraw()
        application.save()
        messages.success(request, "Application withdrawn.")
    except Exception as exc:  # noqa: BLE001
        messages.error(request, f"Could not withdraw: {exc}")
    return redirect("smehub_investment:funding_application_detail", pk=application.pk)


@require_POST
def funding_application_decide_view(request, pk):
    if not _is_sme_admin(request.user):
        raise PermissionDenied()
    application = get_object_or_404(FundingApplication, pk=pk)
    form = FundingApplicationDecisionForm(request.POST)
    if not form.is_valid():
        messages.error(request, "Decision required.")
        return redirect("smehub_investment:funding_application_detail", pk=application.pk)
    try:
        services.decide_funding_application(
            application,
            action=form.cleaned_data["action"],
            by_user=request.user,
            note=form.cleaned_data.get("note", ""),
        )
        messages.success(
            request, f"Application {form.cleaned_data['action'].replace('_', ' ')}.",
        )
    except services.InvestmentStateError as exc:
        messages.error(request, str(exc))
    return redirect("smehub_investment:funding_application_detail", pk=application.pk)


class MyFundingApplicationsView(EntrepreneurRequired, LoginRequiredMixin, ListView):
    template_name = "smehub/investment/my_funding_applications.html"
    context_object_name = "applications"
    paginate_by = 20

    def get_queryset(self):
        entrepreneur = _entrepreneur_profile(self.request.user)
        if entrepreneur is None:
            return FundingApplication.objects.none()
        return (
            FundingApplication.objects.filter(entrepreneur=entrepreneur)
            .select_related("funding_call", "business")
            .order_by("-updated_at")
        )


# ---------------------------------------------------------------------------
# Investor matchmaking
# ---------------------------------------------------------------------------

class InvestorMatchListView(EntrepreneurRequired, LoginRequiredMixin, TemplateView):
    template_name = "smehub/investment/investor_match_list.html"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        entrepreneur = _entrepreneur_profile(self.request.user)
        if entrepreneur is None:
            ctx["matches"] = []
            return ctx
        if not InvestorMatch.objects.filter(entrepreneur=entrepreneur).exists():
            services.refresh_matches_for(entrepreneur, trigger="page_view")
        ctx["matches"] = (
            InvestorMatch.objects.filter(entrepreneur=entrepreneur)
            .select_related("investor")
            .order_by("-score")[:20]
        )
        ctx["entrepreneur"] = entrepreneur
        ctx["readiness"] = (
            InvestmentReadiness.objects.filter(entrepreneur=entrepreneur).first()
            or services.compute_readiness(entrepreneur)
        )
        return ctx


@require_POST
def investor_match_refresh_view(request):
    entrepreneur = _entrepreneur_profile(request.user)
    if entrepreneur is None:
        raise PermissionDenied()
    services.refresh_matches_for(entrepreneur, trigger="manual_refresh")
    messages.success(request, "Investor matches refreshed.")
    return redirect("smehub_investment:investor_match_list")


# ---------------------------------------------------------------------------
# SME performance dashboard
# ---------------------------------------------------------------------------

class SMEPerformanceDashboardView(EntrepreneurRequired, LoginRequiredMixin, TemplateView):
    template_name = "smehub/investment/sme_performance_dashboard.html"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        entrepreneur = _entrepreneur_profile(self.request.user)
        ctx["entrepreneur"] = entrepreneur
        if entrepreneur is None:
            return ctx
        snapshot = SMEPerformanceSnapshot.objects.filter(entrepreneur=entrepreneur).first()
        if snapshot is None:
            snapshot = services.compile_performance_snapshot(entrepreneur, trigger="dashboard_view")
        ctx["snapshot"] = snapshot
        ctx["payload"] = snapshot.payload if snapshot else {}
        ctx["readiness"] = (
            InvestmentReadiness.objects.filter(entrepreneur=entrepreneur).first()
            or services.compute_readiness(entrepreneur)
        )
        ctx["share_form"] = ShareDashboardForm()
        ctx["recent_shares"] = (
            DashboardAccessLog.objects.filter(entrepreneur=entrepreneur)
            .select_related("investor")
            .order_by("-accessed_at")[:6]
        )
        return ctx


@require_POST
def share_dashboard_view(request):
    entrepreneur = _entrepreneur_profile(request.user)
    if entrepreneur is None:
        raise PermissionDenied()
    form = ShareDashboardForm(request.POST)
    if not form.is_valid():
        messages.error(request, "Pick an investor and scope.")
        return redirect("smehub_investment:performance_dashboard")
    try:
        log = services.share_dashboard_with_investor(
            entrepreneur=entrepreneur,
            investor=form.cleaned_data["investor"],
            scope=form.cleaned_data["scope"],
            note=form.cleaned_data.get("note", ""),
        )
    except services.InvestmentStateError as exc:
        messages.error(request, str(exc))
        return redirect("smehub_investment:performance_dashboard")
    share_url = request.build_absolute_uri(
        reverse(
            "smehub_investment:performance_dashboard_shared",
            kwargs={"token": str(log.share_token)},
        )
    )
    messages.success(
        request,
        f"Dashboard shared with {log.investor.org_name}. Read-only link: {share_url}",
    )
    return redirect("smehub_investment:performance_dashboard")


@require_POST
def revoke_dashboard_share_view(request, pk: int):
    """Entrepreneur revokes a previously-shared dashboard link.

    Sets ``revoked_at`` so SMEPerformanceSharedView will reject the token via
    its ``is_active`` check. Only the entrepreneur who owns the share can
    revoke it.
    """
    entrepreneur = _entrepreneur_profile(request.user)
    if entrepreneur is None:
        raise PermissionDenied()
    log = get_object_or_404(DashboardAccessLog, pk=pk)
    if log.entrepreneur_id != entrepreneur.pk:
        raise PermissionDenied()
    if log.revoked_at is None:
        from django.utils import timezone as _tz

        log.revoked_at = _tz.now()
        log.save(update_fields=["revoked_at"])
        messages.success(request, "Dashboard share revoked.")
    return redirect("smehub_investment:performance_dashboard")


class SMEPerformanceSharedView(LoginRequiredMixin, TemplateView):
    """FRSME-INV012 — read-only investor dashboard.

    Phase 5 audit fix: previously any authenticated user with the share
    URL could view the dashboard indefinitely, with no per-load audit row.
    The hardened view now:

    * resolves the access log by token AND verifies the requester is the
      investor's contact_user (or an SME admin / the entrepreneur owner);
    * rejects the request when the share has expired or been revoked;
    * writes a fresh ``DashboardViewEvent`` row on every render so the audit
      trail captures the actual reader, IP, user-agent, and timestamp;
    * bumps the parent log's ``view_count`` and ``last_viewed_at``.
    """

    template_name = "smehub/investment/sme_performance_dashboard_investor.html"

    def _user_may_view(self, request, log: DashboardAccessLog) -> bool:
        user = request.user
        if user.is_superuser or user.role in SME_ADMIN_ROLES:
            return True
        if log.entrepreneur.user_id == user.pk:
            return True  # the entrepreneur viewing their own share is fine
        if log.investor and log.investor.contact_user_id == user.pk:
            return True
        return False

    def get(self, request, *args, **kwargs):
        token = kwargs.get("token")
        log = get_object_or_404(DashboardAccessLog, share_token=token)
        if not log.is_active:
            raise PermissionDenied("This dashboard share has expired or been revoked.")
        if not self._user_may_view(request, log):
            raise PermissionDenied("You are not authorised to view this dashboard.")

        # Per-load audit row + parent counter.
        DashboardViewEvent.objects.create(
            access_log=log,
            viewer_user=request.user if request.user.is_authenticated else None,
            ip_address=request.META.get("REMOTE_ADDR"),
            user_agent=request.META.get("HTTP_USER_AGENT", "")[:512],
        )
        DashboardAccessLog.objects.filter(pk=log.pk).update(
            view_count=models.F("view_count") + 1,
            last_viewed_at=timezone.now(),
        )
        return super().get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        token = kwargs.get("token")
        log = get_object_or_404(DashboardAccessLog, share_token=token)
        ctx["log"] = log
        ctx["entrepreneur"] = log.entrepreneur
        ctx["investor"] = log.investor
        snapshot = SMEPerformanceSnapshot.objects.filter(entrepreneur=log.entrepreneur).first()
        if snapshot is None:
            snapshot = services.compile_performance_snapshot(
                log.entrepreneur, trigger="shared_view",
            )
        ctx["snapshot"] = snapshot
        ctx["payload"] = snapshot.payload if snapshot else {}
        return ctx


# ---------------------------------------------------------------------------
# Investor pitch flow
# ---------------------------------------------------------------------------

class InvestorPitchRequestView(EntrepreneurRequired, LoginRequiredMixin, View):
    template_name = "smehub/investment/investor_pitch_request_form.html"

    def get(self, request, pk):
        investor = get_object_or_404(Investor, pk=pk)
        entrepreneur = _entrepreneur_profile(request.user)
        form = InvestorConnectionRequestForm(entrepreneur=entrepreneur)
        return render(request, self.template_name, {
            "investor": investor, "form": form, "entrepreneur": entrepreneur,
        })

    def post(self, request, pk):
        investor = get_object_or_404(Investor, pk=pk)
        entrepreneur = _entrepreneur_profile(request.user)
        form = InvestorConnectionRequestForm(request.POST, entrepreneur=entrepreneur)
        if not form.is_valid():
            return render(request, self.template_name, {
                "investor": investor, "form": form, "entrepreneur": entrepreneur,
            })
        try:
            req = services.request_investor_connection(
                entrepreneur=entrepreneur,
                business=form.cleaned_data["business"],
                investor=investor,
                message=form.cleaned_data.get("message", ""),
                funding_ask=form.cleaned_data.get("funding_ask"),
                supporting_link=form.cleaned_data.get("supporting_link", ""),
            )
        except services.InvestmentStateError as exc:
            messages.error(request, str(exc))
            return redirect("smehub_investment:investor_detail", pk=investor.pk)
        messages.success(request, "Pitch request sent.")
        return redirect("smehub_investment:investor_request_detail", pk=req.pk)


class MyInvestorRequestsView(EntrepreneurRequired, LoginRequiredMixin, ListView):
    template_name = "smehub/investment/my_investor_requests.html"
    context_object_name = "requests"
    paginate_by = 20

    def get_queryset(self):
        entrepreneur = _entrepreneur_profile(self.request.user)
        if entrepreneur is None:
            return InvestorConnectionRequest.objects.none()
        return (
            InvestorConnectionRequest.objects.filter(entrepreneur=entrepreneur)
            .select_related("investor", "business", "deal_room")
            .order_by("status", "-created_at")
        )


class InvestorPitchInboxView(InvestorRequired, LoginRequiredMixin, ListView):
    template_name = "smehub/investment/investor_pitch_inbox.html"
    context_object_name = "requests"
    paginate_by = 20

    def get_queryset(self):
        investor = _investor_for_user(self.request.user)
        if investor is None:
            return InvestorConnectionRequest.objects.none()
        return (
            InvestorConnectionRequest.objects.filter(investor=investor)
            .select_related("entrepreneur__user", "business", "deal_room")
            .order_by("status", "-created_at")
        )

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["investor"] = _investor_for_user(self.request.user)
        ctx["decline_form"] = InvestorRequestDeclineForm()
        return ctx


class InvestorRequestDetailView(LoginRequiredMixin, DetailView):
    model = InvestorConnectionRequest
    template_name = "smehub/investment/investor_request_detail.html"
    context_object_name = "request_obj"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        request_obj: InvestorConnectionRequest = ctx["request_obj"]
        is_admin = _is_sme_admin(self.request.user)
        is_entrepreneur = request_obj.entrepreneur.user_id == self.request.user.pk
        is_investor = (
            request_obj.investor.contact_user_id == self.request.user.pk
            if request_obj.investor else False
        )
        if not (is_admin or is_entrepreneur or is_investor):
            raise PermissionDenied()
        ctx["is_admin"] = is_admin
        ctx["is_entrepreneur"] = is_entrepreneur
        ctx["is_investor"] = is_investor
        ctx["can_decide"] = is_admin or is_investor
        ctx["decline_form"] = InvestorRequestDeclineForm()
        return ctx


@require_POST
def investor_request_accept_view(request, pk):
    request_obj = get_object_or_404(InvestorConnectionRequest, pk=pk)
    if not _investor_can_decide_request(request.user, request_obj):
        raise PermissionDenied()
    try:
        services.accept_investor_request(request_obj, by_user=request.user)
        messages.success(request, "Request accepted — deal room opened.")
    except services.InvestmentStateError as exc:
        messages.error(request, str(exc))
    return redirect("smehub_investment:investor_request_detail", pk=request_obj.pk)


@require_POST
def investor_request_decline_view(request, pk):
    request_obj = get_object_or_404(InvestorConnectionRequest, pk=pk)
    if not _investor_can_decide_request(request.user, request_obj):
        raise PermissionDenied()
    form = InvestorRequestDeclineForm(request.POST)
    reason = form.cleaned_data.get("reason", "") if form.is_valid() else ""
    try:
        services.decline_investor_request(request_obj, by_user=request.user, reason=reason)
        messages.success(request, "Request declined.")
    except services.InvestmentStateError as exc:
        messages.error(request, str(exc))
    return redirect("smehub_investment:investor_request_detail", pk=request_obj.pk)


@require_POST
def investor_request_withdraw_view(request, pk):
    request_obj = get_object_or_404(InvestorConnectionRequest, pk=pk)
    if request_obj.entrepreneur.user_id != request.user.pk:
        raise PermissionDenied()
    try:
        services.withdraw_investor_request(request_obj, by_user=request.user)
        messages.success(request, "Request withdrawn.")
    except services.InvestmentStateError as exc:
        messages.error(request, str(exc))
    return redirect("smehub_investment:my_investor_requests")


# ---------------------------------------------------------------------------
# Manual funding records
# ---------------------------------------------------------------------------

class ManualFundingListView(SMEAdminRequired, LoginRequiredMixin, ListView):
    template_name = "smehub/investment/manual_funding_list.html"
    context_object_name = "records"
    paginate_by = 25

    def get_queryset(self):
        return (
            ManualFundingRecord.objects.select_related(
                "entrepreneur__user", "business", "investor", "captured_by_admin",
            )
            .order_by("-funded_at")
        )

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["total"] = (
            ManualFundingRecord.objects.aggregate(v=Sum("amount")).get("v") or Decimal("0")
        )
        return ctx


class ManualFundingCreateView(SMEAdminRequired, LoginRequiredMixin, View):
    template_name = "smehub/investment/manual_funding_form.html"

    def get(self, request):
        form = ManualFundingRecordForm()
        return render(request, self.template_name, {"form": form})

    def post(self, request):
        form = ManualFundingRecordForm(request.POST)
        if not form.is_valid():
            return render(request, self.template_name, {"form": form})
        try:
            record = services.record_manual_funding(
                by_admin=request.user,
                entrepreneur=form.cleaned_data["entrepreneur"],
                business=form.cleaned_data["business"],
                funder_name=form.cleaned_data["funder_name"],
                amount=form.cleaned_data["amount"],
                funded_at=form.cleaned_data["funded_at"],
                currency=form.cleaned_data.get("currency", "USD"),
                investor=form.cleaned_data.get("investor"),
                source_note=form.cleaned_data.get("source_note", ""),
            )
        except (services.InvestmentStateError, ValidationError) as exc:
            messages.error(request, str(exc))
            return render(request, self.template_name, {"form": form})
        messages.success(
            request,
            f"Recorded {record.currency} {record.amount} for {record.business.name}.",
        )
        return redirect("smehub_investment:manual_funding_list")


# ---------------------------------------------------------------------------
# Investor workspace — landing dashboard + portfolio
# ---------------------------------------------------------------------------

class InvestorDashboardView(InvestorRequired, LoginRequiredMixin, TemplateView):
    template_name = "smehub/investment/investor_dashboard.html"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        user = self.request.user
        investor = _investor_for_user(user)
        ctx["investor"] = investor
        if investor is None:
            return ctx

        requests_qs = InvestorConnectionRequest.objects.filter(investor=investor)
        accepted_qs = requests_qs.filter(status=InvestorConnectionRequest.Status.ACCEPTED)

        # Open deal rooms — reach via accepted requests' deal_room FK.
        open_deal_room_ids = list(
            accepted_qs.exclude(deal_room__isnull=True)
            .filter(deal_room__status="open")
            .values_list("deal_room_id", flat=True)
        )

        ctx["stats"] = {
            "pending": requests_qs.filter(status=InvestorConnectionRequest.Status.PENDING).count(),
            "accepted": accepted_qs.count(),
            "declined": requests_qs.filter(status=InvestorConnectionRequest.Status.DECLINED).count(),
            "open_deal_rooms": len(open_deal_room_ids),
            "manual_funded": ManualFundingRecord.objects.filter(investor=investor).count(),
        }
        ctx["pending_requests"] = (
            requests_qs.filter(status=InvestorConnectionRequest.Status.PENDING)
            .select_related("entrepreneur__user", "business")
            .order_by("-created_at")[:5]
        )
        ctx["open_deal_rooms"] = (
            accepted_qs.exclude(deal_room__isnull=True)
            .filter(deal_room__status="open")
            .select_related("entrepreneur__user", "business", "deal_room")
            .order_by("-deal_room__last_activity_at")[:5]
        )
        ctx["recent_accepted"] = (
            accepted_qs.select_related("entrepreneur__user", "business", "deal_room")
            .order_by("-decided_at")[:5]
        )

        return ctx


class InvestorPortfolioView(InvestorRequired, LoginRequiredMixin, ListView):
    template_name = "smehub/investment/investor_portfolio.html"
    context_object_name = "requests"
    paginate_by = 30

    def get_queryset(self):
        investor = _investor_for_user(self.request.user)
        if investor is None:
            return InvestorConnectionRequest.objects.none()
        return (
            InvestorConnectionRequest.objects.filter(
                investor=investor,
                status=InvestorConnectionRequest.Status.ACCEPTED,
            )
            .select_related("entrepreneur__user", "business", "deal_room")
            .order_by("-decided_at")
        )

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        investor = _investor_for_user(self.request.user)
        ctx["investor"] = investor
        if investor is not None:
            ctx["funded_records"] = (
                ManualFundingRecord.objects.filter(investor=investor)
                .select_related("business", "entrepreneur__user")
                .order_by("-funded_at")
            )
        else:
            ctx["funded_records"] = ManualFundingRecord.objects.none()
        return ctx
