"""Forms for M&EL Activity & OutputDeliverable CRUD (FRMFL009-019).

ModelForms restrict ``logframe_row`` to the appropriate ``LogFrameLevel`` so
the dropdown surfaces only valid rows. Status changes flow through
``ActivityTransitionForm`` + ``record_activity_progress`` rather than the
ModelForm — the FSM matrix and audit signal stay in the service layer.
"""
from __future__ import annotations

from django import forms

from apps.mel.indicators.models import LogFrameLevel, LogFrameRow
from apps.mel.tracking.models import (
    Activity,
    ActivityStatus,
    CorrectiveAction,
    OutputDeliverable,
)


class ActivityForm(forms.ModelForm):
    class Meta:
        model = Activity
        fields = [
            "logframe_row",
            "name",
            "description",
            "scheduled_start",
            "scheduled_end",
            "responsible",
            "parent",
        ]
        widgets = {
            "scheduled_start": forms.DateInput(attrs={"type": "date"}),
            "scheduled_end": forms.DateInput(attrs={"type": "date"}),
            "description": forms.Textarea(attrs={"rows": 4}),
            "responsible": forms.Select(attrs={"data-select2-user": "1"}),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["logframe_row"].queryset = (
            LogFrameRow.objects.filter(level=LogFrameLevel.ACTIVITY)
            .select_related("logframe")
            .order_by("logframe__name", "order", "pk")
        )
        self.fields["logframe_row"].label = "Strategic Objective row"
        self.fields["logframe_row"].help_text = (
            "Pick the Activity-level row this work contributes to."
        )
        parent_qs = Activity.objects.select_related("logframe_row")
        if self.instance.pk:
            parent_qs = parent_qs.exclude(pk=self.instance.pk)
        self.fields["parent"].queryset = parent_qs
        self.fields["parent"].required = False
        # Searchable Select2 (jQuery/Select2 loaded via the form's core scripts).
        self.fields["logframe_row"].widget.attrs.update(
            {"data-s2": True, "data-s2-placeholder": "Search activity rows…"}
        )
        self.fields["parent"].widget.attrs.update(
            {"data-s2": True, "data-s2-placeholder": "Search parent activities…"}
        )


class OutputDeliverableForm(forms.ModelForm):
    class Meta:
        model = OutputDeliverable
        fields = [
            "logframe_row",
            "title",
            "description",
            "target_quantity",
            "achieved_quantity",
            "unit",
            "due_date",
            "evidence_url",
        ]
        widgets = {
            "due_date": forms.DateInput(attrs={"type": "date"}),
            "description": forms.Textarea(attrs={"rows": 4}),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["logframe_row"].queryset = (
            LogFrameRow.objects.filter(level=LogFrameLevel.OUTPUT)
            .select_related("logframe")
            .order_by("logframe__name", "order", "pk")
        )
        self.fields["logframe_row"].label = "Strategic Objective row"
        self.fields["logframe_row"].help_text = (
            "Pick the Output-level row this deliverable contributes to."
        )
        self.fields["logframe_row"].widget.attrs.update(
            {"data-s2": True, "data-s2-placeholder": "Search output rows…"}
        )


class ActivityTransitionForm(forms.Form):
    """Validate a status transition POST — service enforces the FSM matrix."""

    new_status = forms.ChoiceField(choices=ActivityStatus.choices)
    deviation_reason = forms.CharField(
        required=False,
        max_length=500,
        widget=forms.Textarea(attrs={"rows": 2}),
    )
    actual_start = forms.DateField(
        required=False,
        widget=forms.DateInput(attrs={"type": "date"}),
    )
    actual_end = forms.DateField(
        required=False,
        widget=forms.DateInput(attrs={"type": "date"}),
    )


class FollowupMonitoringForm(forms.Form):
    """M&E SRS Table 65 — schedule follow-up monitoring for an incomplete activity."""

    name = forms.CharField(
        required=False,
        max_length=255,
        widget=forms.TextInput(
            attrs={"placeholder": "Leave blank to auto-name from the original"}
        ),
        help_text="Name for the follow-up monitoring activity.",
    )
    scheduled_start = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}))
    scheduled_end = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}))

    def clean(self):
        cleaned = super().clean()
        start, end = cleaned.get("scheduled_start"), cleaned.get("scheduled_end")
        if start and end and end < start:
            self.add_error("scheduled_end", "End date must be on or after the start date.")
        return cleaned


class RecordOutputProgressForm(forms.Form):
    achieved = forms.DecimalField(min_value=0, max_digits=14, decimal_places=4)
    evidence_url = forms.URLField(required=False)


class CorrectiveActionCreateForm(forms.ModelForm):
    """Officer-facing form to open a CorrectiveAction against any subject.

    Subject (content type + object id) is supplied as hidden fields by the
    caller (e.g., variance page pre-fills subject = current indicator).
    """

    class Meta:
        model = CorrectiveAction
        fields = ["title", "severity", "root_cause", "action_plan", "due_date", "owner"]
        widgets = {
            "root_cause": forms.Textarea(attrs={"rows": 3}),
            "action_plan": forms.Textarea(attrs={"rows": 3}),
            "due_date": forms.DateInput(attrs={"type": "date"}),
            "owner": forms.Select(
                attrs={"data-s2": True, "data-s2-placeholder": "Search staff…"}
            ),
        }

    def clean(self):
        cleaned = super().clean()
        # M&E SRS Table 66 — "Every high-risk or critical issue shall have a
        # corrective action assigned with a responsible officer and due date."
        from apps.mel.tracking.models import CorrectiveSeverity

        severity = cleaned.get("severity")
        if severity in (CorrectiveSeverity.HIGH, CorrectiveSeverity.CRITICAL):
            if not cleaned.get("owner"):
                self.add_error(
                    "owner",
                    "High/critical corrective actions need a responsible officer.",
                )
            if not cleaned.get("due_date"):
                self.add_error(
                    "due_date",
                    "High/critical corrective actions need a due date.",
                )
        return cleaned


# ---------------------------------------------------------------------------
# G2 — public attendance form (token-gated, no auth required)
# ---------------------------------------------------------------------------


class PublicAttendanceForm(forms.Form):
    """Minimal-friction form rendered by the QR-scan landing page.

    Fields are individually optional; the view enforces "at least one of
    name / email / phone is present" so anonymous attendance still works
    when an event chooses not to collect personal data.
    """

    attendee_name = forms.CharField(
        label="Full name",
        max_length=255,
        required=False,
        widget=forms.TextInput(attrs={
            "autocomplete": "name", "inputmode": "text",
        }),
    )
    attendee_email = forms.EmailField(
        label="Email",
        required=False,
        widget=forms.EmailInput(attrs={
            "autocomplete": "email", "inputmode": "email",
        }),
    )
    attendee_phone = forms.CharField(
        label="Phone",
        max_length=40,
        required=False,
        widget=forms.TextInput(attrs={
            "autocomplete": "tel", "inputmode": "tel",
        }),
    )
    organisation = forms.CharField(
        label="Organisation",
        max_length=255,
        required=False,
        widget=forms.TextInput(attrs={"autocomplete": "organization"}),
    )
    notes = forms.CharField(
        label="Notes (optional)",
        required=False,
        widget=forms.Textarea(attrs={"rows": 2}),
    )
    consent = forms.BooleanField(
        label="I consent to being recorded as attending this event.",
        required=True,
    )

    def clean(self):
        cleaned = super().clean()
        if not any(
            cleaned.get(f) for f in ("attendee_name", "attendee_email", "attendee_phone")
        ):
            raise forms.ValidationError(
                "Please provide at least one of name, email, or phone."
            )
        return cleaned
