"""SP4 showcasing forms — events, challenges, applications, deal rooms.

All multi-selects use ``data-select2`` attrs so the project's select2 init
partial enhances them automatically.
"""
from __future__ import annotations

from decimal import Decimal

from django import forms
from django.core.exceptions import ValidationError
from django.forms import inlineformset_factory

from apps.core.permissions.roles import UserRole
from apps.smehub._shared.choices import SECTORS
from apps.smehub._shared.validators import (
    DEAL_ROOM_ATTACHMENT_VALIDATOR,
    DEAL_ROOM_DOCUMENT_VALIDATOR,
    PITCH_DECK_VALIDATOR,
    SIGNED_AGREEMENT_VALIDATOR,
)
from apps.smehub.showcasing.models import (
    ChallengeRubricCategory,
    ChallengeRubricSection,
    ChallengeScoringRubric,
    DealRoomDocument,
    DealRoomMessage,
    InnovationCatalogueEntry,
    InnovationChallenge,
    PitchScore,
    ShowcaseApplication,
    ShowcaseEvent,
)


INPUT_BASE = (
    "block w-full rounded-lg border border-ruforum-border bg-white px-3 py-2 "
    "text-sm text-ruforum-text shadow-sm placeholder-ruforum-text-muted/70 "
    "focus:border-ruforum-primary focus:outline-none focus:ring-1 focus:ring-ruforum-primary"
)
TEXTAREA_BASE = INPUT_BASE + " min-h-[7rem]"
SELECT_BASE = INPUT_BASE


def _s2_single(*, placeholder: str = "") -> dict:
    attrs = {"class": SELECT_BASE, "data-s2": ""}
    if placeholder:
        attrs["data-s2-placeholder"] = placeholder
    return attrs


def _s2_multi(*, placeholder: str) -> dict:
    return {
        "class": SELECT_BASE + " min-h-[2.5rem]",
        "data-s2": "",
        "data-s2-placeholder": placeholder,
    }


# ---------------------------------------------------------------------------
# Showcase events
# ---------------------------------------------------------------------------

class ShowcaseEventForm(forms.ModelForm):
    target_sectors = forms.MultipleChoiceField(
        choices=SECTORS,
        required=False,
        widget=forms.SelectMultiple(attrs=_s2_multi(placeholder="Target sectors")),
    )
    evaluation_panel = forms.ModelMultipleChoiceField(
        queryset=None,  # set in __init__
        required=False,
        widget=forms.SelectMultiple(attrs=_s2_multi(placeholder="Pick judges from the panel")),
    )

    class Meta:
        model = ShowcaseEvent
        fields = [
            "name",
            "summary",
            "description",
            "format",
            "starts_at",
            "ends_at",
            "application_deadline",
            "venue",
            "video_link",
            "cover_image",
            "target_sectors",
            "evaluation_panel",
        ]
        widgets = {
            "name": forms.TextInput(attrs={"class": INPUT_BASE}),
            "summary": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 3}),
            "description": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 6}),
            "format": forms.Select(attrs=_s2_single(placeholder="Pick format")),
            "starts_at": forms.DateTimeInput(
                attrs={"class": INPUT_BASE, "type": "datetime-local"},
            ),
            "ends_at": forms.DateTimeInput(
                attrs={"class": INPUT_BASE, "type": "datetime-local"},
            ),
            "application_deadline": forms.DateTimeInput(
                attrs={"class": INPUT_BASE, "type": "datetime-local"},
            ),
            "venue": forms.TextInput(
                attrs={"class": INPUT_BASE, "placeholder": "Venue or platform link"},
            ),
            "video_link": forms.URLInput(
                attrs={"class": INPUT_BASE, "placeholder": "https://"},
            ),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        from django.contrib.auth import get_user_model

        User = get_user_model()
        self.fields["evaluation_panel"].queryset = User.objects.filter(
            role=UserRole.JUDGE, is_active=True,
        ).order_by("first_name", "email")
        if self.instance.pk:
            self.initial["target_sectors"] = self.instance.target_sectors
            self.initial["evaluation_panel"] = list(
                self.instance.evaluation_panel.values_list("pk", flat=True),
            )

    def clean(self):
        cleaned = super().clean()
        starts = cleaned.get("starts_at")
        ends = cleaned.get("ends_at")
        deadline = cleaned.get("application_deadline")
        if starts and ends and ends < starts:
            self.add_error("ends_at", "End time must be on or after the start time.")
        if starts and deadline and deadline > starts:
            self.add_error("application_deadline", "Deadline must be before the event start.")
        return cleaned


# ---------------------------------------------------------------------------
# Innovation challenges
# ---------------------------------------------------------------------------

class InnovationChallengeForm(forms.ModelForm):
    target_sectors = forms.MultipleChoiceField(
        choices=SECTORS,
        required=False,
        widget=forms.SelectMultiple(attrs=_s2_multi(placeholder="Target sectors")),
    )

    class Meta:
        model = InnovationChallenge
        fields = [
            "title",
            "summary",
            "description",
            "theme",
            "prize_summary",
            "application_deadline",
            "judging_deadline",
            "cover_image",
            "target_sectors",
        ]
        widgets = {
            "title": forms.TextInput(attrs={"class": INPUT_BASE}),
            "summary": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 3}),
            "description": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 6}),
            "theme": forms.TextInput(attrs={"class": INPUT_BASE}),
            "prize_summary": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 3}),
            "application_deadline": forms.DateTimeInput(
                attrs={"class": INPUT_BASE, "type": "datetime-local"},
            ),
            "judging_deadline": forms.DateTimeInput(
                attrs={"class": INPUT_BASE, "type": "datetime-local"},
            ),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instance.pk:
            self.initial["target_sectors"] = self.instance.target_sectors


class ChallengeRubricCategoryForm(forms.ModelForm):
    class Meta:
        model = ChallengeRubricCategory
        fields = ["title", "points", "order"]
        widgets = {
            "title": forms.TextInput(attrs={"class": INPUT_BASE}),
            "points": forms.NumberInput(attrs={"class": INPUT_BASE, "min": "0"}),
            "order": forms.NumberInput(attrs={"class": INPUT_BASE, "min": "0"}),
        }


ChallengeRubricCategoryFormSet = inlineformset_factory(
    ChallengeScoringRubric,
    ChallengeRubricCategory,
    form=ChallengeRubricCategoryForm,
    extra=1,
    can_delete=True,
)


# Score-band anchor descriptions are edited as five plain text fields that pack
# into the criterion's ``score_anchors`` JSON on save.
_ANCHOR_SCORES = [5, 4, 3, 2, 1]


class ChallengeRubricSectionForm(forms.ModelForm):
    category = forms.ModelChoiceField(
        queryset=ChallengeRubricCategory.objects.none(),
        required=False,
        widget=forms.Select(attrs={"class": SELECT_BASE}),
        help_text="Group this criterion under a rubric category.",
    )

    class Meta:
        model = ChallengeRubricSection
        fields = ["category", "title", "instructions", "weight", "max_marks", "order"]
        widgets = {
            "title": forms.TextInput(attrs={"class": INPUT_BASE}),
            "instructions": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 2}),
            "weight": forms.NumberInput(
                attrs={"class": INPUT_BASE, "step": "0.01", "min": "0", "max": "1"},
            ),
            "max_marks": forms.NumberInput(attrs={"class": INPUT_BASE, "min": "1"}),
            "order": forms.NumberInput(attrs={"class": INPUT_BASE, "min": "0"}),
        }

    def __init__(self, *args, rubric=None, **kwargs):
        super().__init__(*args, **kwargs)
        if rubric is not None:
            self.fields["category"].queryset = rubric.categories.all()
        # New/unsaved criteria start with a blank weight rather than the model's
        # 0.25 default — otherwise fresh rows inflate the live weight total (and
        # the "must sum to 1.0" hint) before the admin has typed anything.
        if not (self.instance and self.instance.pk):
            self.fields["weight"].initial = None
        # Seed the five anchor text fields from the stored JSON.
        anchors = (self.instance.score_anchors or {}) if self.instance else {}
        for score in _ANCHOR_SCORES:
            self.fields[f"anchor_{score}"] = forms.CharField(
                required=False,
                initial=anchors.get(str(score), ""),
                widget=forms.TextInput(
                    attrs={"class": INPUT_BASE, "placeholder": f"Score {score} description"},
                ),
            )

    @property
    def anchor_fields(self):
        """The five anchor bound-fields, highest score first, for templates."""
        return [(score, self[f"anchor_{score}"]) for score in _ANCHOR_SCORES]

    def save(self, commit=True):
        instance = super().save(commit=False)
        anchors = {}
        for score in _ANCHOR_SCORES:
            val = (self.cleaned_data.get(f"anchor_{score}") or "").strip()
            if val:
                anchors[str(score)] = val
        instance.score_anchors = anchors
        if commit:
            instance.save()
        return instance


ChallengeRubricSectionFormSet = inlineformset_factory(
    ChallengeScoringRubric,
    ChallengeRubricSection,
    form=ChallengeRubricSectionForm,
    extra=0,
    can_delete=True,
)


# ---------------------------------------------------------------------------
# Applications
# ---------------------------------------------------------------------------

class _ShowcaseApplicationFormBase(forms.ModelForm):
    business = forms.ModelChoiceField(
        queryset=None,  # set in __init__
        widget=forms.Select(attrs=_s2_single(placeholder="Pick the business behind this entry")),
    )

    class Meta:
        model = ShowcaseApplication
        fields = [
            "business",
            "innovation_title",
            "innovation_description",
            "problem_statement",
            "solution_summary",
            "impact_potential",
            "pitch_deck",
            "pitch_video_link",
            "supporting_link",
        ]
        widgets = {
            "innovation_title": forms.TextInput(attrs={"class": INPUT_BASE}),
            "innovation_description": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 4}),
            "problem_statement": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 4}),
            "solution_summary": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 4}),
            "impact_potential": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 4}),
            "pitch_video_link": forms.URLInput(
                attrs={"class": INPUT_BASE, "placeholder": "Optional video URL"},
            ),
            "supporting_link": forms.URLInput(
                attrs={"class": INPUT_BASE, "placeholder": "Optional supporting link"},
            ),
        }

    def __init__(self, *args, entrepreneur=None, draft=False, **kwargs):
        super().__init__(*args, **kwargs)
        from apps.smehub.onboarding.models import Business

        self.entrepreneur = entrepreneur
        # FRSME-ISD004 — a draft save persists partial input, so every content
        # field is optional. Only the business (the row's owner) stays required.
        self.draft = draft
        qs = Business.objects.none()
        if entrepreneur is not None:
            qs = Business.objects.filter(
                entrepreneur=entrepreneur,
                verification_status=Business.Status.VERIFIED,
            )
        self.fields["business"].queryset = qs
        if draft:
            for name, field in self.fields.items():
                if name != "business":
                    field.required = False
        # FRSME-ISD safety — bound the pitch deck to safe extensions / size.
        if "pitch_deck" in self.fields:
            self.fields["pitch_deck"].validators = [
                *self.fields["pitch_deck"].validators,
                PITCH_DECK_VALIDATOR,
            ]

    def clean(self):
        cleaned = super().clean()
        if self.draft:
            return cleaned
        # FRSME-ISD005 — at least one pitch material is mandatory on submit.
        if not (
            cleaned.get("pitch_deck")
            or cleaned.get("pitch_video_link")
            or cleaned.get("supporting_link")
        ):
            raise ValidationError(
                "Provide at least one pitch material — a pitch deck, a pitch video link, or a supporting link.",
            )
        return cleaned


class ApplyToEventForm(_ShowcaseApplicationFormBase):
    pass


class ApplyToChallengeForm(_ShowcaseApplicationFormBase):
    pass


class ConfirmParticipantsForm(forms.Form):
    application_ids = forms.CharField(widget=forms.HiddenInput())
    note = forms.CharField(
        required=False,
        widget=forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 3, "placeholder": "Optional note for confirmed participants"}),
    )

    def clean_application_ids(self):
        raw = self.cleaned_data["application_ids"].strip()
        if not raw:
            raise ValidationError("Pick at least one application to confirm.")
        try:
            ids = [int(part) for part in raw.split(",") if part]
        except ValueError as exc:
            raise ValidationError("Invalid application IDs.") from exc
        if not ids:
            raise ValidationError("Pick at least one application to confirm.")
        return ids


class RejectApplicationForm(forms.Form):
    note = forms.CharField(
        required=False,
        widget=forms.Textarea(
            attrs={"class": TEXTAREA_BASE, "rows": 3, "placeholder": "Optional note to the applicant"},
        ),
    )


# ---------------------------------------------------------------------------
# Catalogue
# ---------------------------------------------------------------------------

class PublishToCatalogueForm(forms.ModelForm):
    sectors = forms.MultipleChoiceField(
        choices=SECTORS,
        required=False,
        widget=forms.SelectMultiple(attrs=_s2_multi(placeholder="Sectors")),
    )

    class Meta:
        model = InnovationCatalogueEntry
        fields = [
            "title",
            "summary",
            "description",
            "sectors",
            "cover_image",
            "pitch_deck",
            "video_link",
            "website",
            "visibility",
        ]
        widgets = {
            "title": forms.TextInput(attrs={"class": INPUT_BASE}),
            "summary": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 3}),
            "description": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 6}),
            "video_link": forms.URLInput(attrs={"class": INPUT_BASE}),
            "website": forms.URLInput(attrs={"class": INPUT_BASE}),
            "visibility": forms.Select(attrs=_s2_single(placeholder="Visibility")),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instance.pk:
            self.initial["sectors"] = self.instance.sectors


class CatalogueUpdateForm(PublishToCatalogueForm):
    notes = forms.CharField(
        required=False,
        widget=forms.Textarea(
            attrs={"class": TEXTAREA_BASE, "rows": 2, "placeholder": "What changed in this version?"},
        ),
    )


# ---------------------------------------------------------------------------
# Attendance + scoring
# ---------------------------------------------------------------------------

class AttendanceForm(forms.Form):
    """Bulk attendance toggle keyed by application_id."""

    attended_application_ids = forms.CharField(required=False, widget=forms.HiddenInput())


class PitchScoreForm(forms.ModelForm):
    class Meta:
        model = PitchScore
        fields = ["score", "comments"]
        widgets = {
            "score": forms.NumberInput(
                attrs={"class": INPUT_BASE, "step": "0.5", "min": "0"},
            ),
            "comments": forms.Textarea(
                attrs={"class": TEXTAREA_BASE, "rows": 3, "placeholder": "Comments visible to admins"},
            ),
        }

    def __init__(self, *args, max_marks: int | None = None, **kwargs):
        super().__init__(*args, **kwargs)
        self._max_marks = max_marks
        if max_marks:
            self.fields["score"].widget.attrs["max"] = str(max_marks)
            self.fields["score"].help_text = f"Score out of {max_marks}."

    def clean_score(self):
        # FRSME-ISD013 — judges cannot enter a score above the rubric ceiling.
        score = self.cleaned_data.get("score")
        if score is None:
            return score
        if score < 0:
            raise ValidationError("Score cannot be negative.")
        if self._max_marks and score > Decimal(str(self._max_marks)):
            raise ValidationError(
                f"Score cannot exceed the maximum of {self._max_marks}.",
            )
        return score


# ---------------------------------------------------------------------------
# Deal rooms
# ---------------------------------------------------------------------------

class OpenDealRoomForm(forms.Form):
    """Admin-initiated deal-room open form.

    Renders three select2-enhanced dropdowns: entrepreneur (verified), business
    (filtered to the picked entrepreneur via JS reload of the form), and a
    pooled counterparty list grouped by kind via optgroups.
    """

    entrepreneur = forms.ModelChoiceField(
        queryset=None,
        widget=forms.Select(attrs=_s2_single(placeholder="Pick the entrepreneur")),
        help_text="Verified entrepreneurs only.",
    )
    business = forms.ModelChoiceField(
        queryset=None,
        widget=forms.Select(attrs=_s2_single(placeholder="Pick the business")),
    )
    counterparty = forms.ChoiceField(
        widget=forms.Select(attrs=_s2_single(placeholder="Pick a counterparty")),
        help_text="Encoded as 'kind:pk' — partner / buyer / service_provider.",
    )
    title = forms.CharField(
        required=False, widget=forms.TextInput(attrs={"class": INPUT_BASE}),
    )
    summary = forms.CharField(
        required=False,
        widget=forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 3}),
    )
    expiry_date = forms.DateTimeField(
        required=False,
        widget=forms.DateTimeInput(attrs={"class": INPUT_BASE, "type": "datetime-local"}),
    )

    def __init__(self, *args, entrepreneur=None, **kwargs):
        super().__init__(*args, **kwargs)
        from apps.smehub.linkage.models import (
            PartnerDirectoryEntry,
            ServiceProviderEntry,
        )
        from apps.smehub.marketplace.models import BuyerRegistryEntry
        from apps.smehub.onboarding.models import Business, EntrepreneurProfile

        self.fields["entrepreneur"].queryset = (
            EntrepreneurProfile.objects.filter(
                verification_status=EntrepreneurProfile.Status.VERIFIED,
            ).select_related("user").order_by("user__first_name", "user__email")
        )

        biz_qs = Business.objects.filter(
            verification_status=Business.Status.VERIFIED,
        )
        if entrepreneur is not None:
            biz_qs = biz_qs.filter(entrepreneur=entrepreneur)
        self.fields["business"].queryset = biz_qs

        # Build grouped counterparty choices
        partner_choices = [
            (f"partner:{p.pk}", p.organisation_name)
            for p in PartnerDirectoryEntry.objects.filter(
                verification_status=PartnerDirectoryEntry.Status.VERIFIED,
            ).only("pk", "organisation_name")
        ]
        buyer_choices = [
            (f"buyer:{b.pk}", b.organisation_name)
            for b in BuyerRegistryEntry.objects.filter(
                verification_status=BuyerRegistryEntry.Status.VERIFIED,
            ).only("pk", "organisation_name")
        ]
        provider_choices = [
            (f"service_provider:{s.pk}", s.organisation_name)
            for s in ServiceProviderEntry.objects.filter(
                verification_status=ServiceProviderEntry.Status.VERIFIED,
            ).only("pk", "organisation_name")
        ]
        self.fields["counterparty"].choices = [
            ("", "—"),
            ("Partners", partner_choices),
            ("Buyers", buyer_choices),
            ("Service providers", provider_choices),
        ]

    def clean_counterparty(self):
        raw = self.cleaned_data.get("counterparty") or ""
        if ":" not in raw:
            raise ValidationError("Pick a counterparty from the list.")
        kind, _, pk = raw.partition(":")
        if kind not in {"partner", "buyer", "service_provider"} or not pk.isdigit():
            raise ValidationError("Invalid counterparty selection.")
        return {"kind": kind, "pk": int(pk)}


class DealRoomMessageForm(forms.ModelForm):
    parent_id = forms.IntegerField(required=False, widget=forms.HiddenInput())

    class Meta:
        model = DealRoomMessage
        fields = ["body", "attachment"]
        widgets = {
            "body": forms.Textarea(
                attrs={
                    "class": TEXTAREA_BASE,
                    "rows": 3,
                    "placeholder": "Write a secure message…",
                },
            ),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if "attachment" in self.fields:
            self.fields["attachment"].validators = [
                *self.fields["attachment"].validators,
                DEAL_ROOM_ATTACHMENT_VALIDATOR,
            ]


class DealRoomDocumentForm(forms.ModelForm):
    class Meta:
        model = DealRoomDocument
        fields = ["label", "kind", "file", "notes"]
        widgets = {
            "label": forms.TextInput(attrs={"class": INPUT_BASE}),
            "kind": forms.Select(attrs=_s2_single(placeholder="Document kind")),
            "notes": forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 2}),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if "file" in self.fields:
            self.fields["file"].validators = [
                *self.fields["file"].validators,
                DEAL_ROOM_DOCUMENT_VALIDATOR,
            ]


class FormaliseAgreementForm(forms.Form):
    title = forms.CharField(widget=forms.TextInput(attrs={"class": INPUT_BASE}))
    summary = forms.CharField(
        required=False,
        widget=forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 3}),
    )
    signed_file = forms.FileField(validators=[SIGNED_AGREEMENT_VALIDATOR])
    notes = forms.CharField(
        required=False,
        widget=forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 2}),
    )


class CloseDealRoomForm(forms.Form):
    reason = forms.CharField(
        required=False,
        widget=forms.Textarea(attrs={"class": TEXTAREA_BASE, "rows": 3}),
    )


# Re-export Decimal helper for view modules that need to rebuild rubric weights
__all__ = [name for name in dir() if not name.startswith("_")]
