"""Phase 7 — FRSME-MEI017/018/019 beneficiary survey lifecycle.

Coverage:
* MEI017 — dispatch sets ``status=SENT`` and a ``due_at`` from the
  configurable response window.
* MEI018 — a FeedbackSubmission against the beneficiary channel flips the
  matching SMEHubBeneficiaryFeedback row to ``COMPLETED`` and links the
  submission.
* MEI019 — the reminder task notifies entrepreneurs whose dispatched
  surveys are past the reminder threshold but not yet completed (and only
  reminds once); the non-response task flips overdue rows to
  ``NON_RESPONDED`` and bumps the tracking-record data quality counter.
"""
from __future__ import annotations

from datetime import timedelta

import pytest
from django.contrib.auth import get_user_model
from django.utils import timezone

from apps.core.permissions.roles import UserRole
from apps.mel.feedback.models import (
    FeedbackChannel,
    FeedbackChannelType,
    FeedbackSeverity,
    FeedbackSubmission,
)
from apps.mel.tracking.models import (
    SMEHubBeneficiaryFeedback,
    SMEHubTrackingRecord,
)
from apps.mel.tracking.smehub_feedback import (
    dispatch_post_incubation_survey,
    mark_completed_for_submission,
)
from apps.mel.tracking.tasks import (
    dispatch_feedback_reminders_task,
    record_feedback_non_responses_task,
)
from apps.smehub.onboarding.models import (
    Business,
    BusinessBaseline,
    BusinessStage,
    EntrepreneurProfile,
)
from apps.smehub.onboarding import signals as onb_signals

User = get_user_model()
pytestmark = pytest.mark.django_db


@pytest.fixture
def admin_user():
    return User.objects.create_user(
        email="p7-mel-admin@example.com", password="x",
        is_superuser=True, is_staff=True, role=UserRole.SYSTEM_ADMIN,
    )


@pytest.fixture
def entrepreneur(admin_user):
    user = User.objects.create_user(
        email="p7-mel-ent@example.com", password="x", role=UserRole.ENTREPRENEUR,
    )
    profile = EntrepreneurProfile.objects.create(user=user, country="KE")
    profile.verify(by_user=admin_user)
    profile.save()
    return profile


@pytest.fixture
def business(entrepreneur, admin_user):
    biz = Business.objects.create(
        entrepreneur=entrepreneur, name="P7 MEL Biz",
        sector="agriculture", business_stage=BusinessStage.MVP, country="KE",
    )
    biz.verify(by_user=admin_user)
    biz.save()
    return biz


@pytest.fixture
def baseline(business):
    return BusinessBaseline.objects.create(
        business=business, business_stage_at_entry=BusinessStage.MVP,
    )


@pytest.fixture
def record(entrepreneur, business, baseline):
    onb_signals.smehub_baseline_initialised.send(
        sender=BusinessBaseline, business=business, baseline=baseline,
        entrepreneur=entrepreneur,
    )
    return SMEHubTrackingRecord.objects.get(entrepreneur=entrepreneur)


class _FakeMember:
    """Minimal stand-in for a CohortMember — only ``pk`` and class are read."""

    def __init__(self, pk: int):
        self.pk = pk


# ---------------------------------------------------------------------------
# MEI017 — dispatch sets due_at + SENT
# ---------------------------------------------------------------------------

def test_dispatch_post_incubation_sets_due_at_and_status(record):
    member = _FakeMember(pk=42)
    # The dispatcher uses ContentType.objects.get_for_model(member.__class__),
    # which requires a real model. Use the entrepreneur's business as a stand-in.
    dispatch_post_incubation_survey(record=record, member=record.business)
    row = SMEHubBeneficiaryFeedback.objects.get(tracking_record=record)
    assert row.status == SMEHubBeneficiaryFeedback.Status.SENT
    assert row.due_at is not None
    assert row.due_at > timezone.now()


# ---------------------------------------------------------------------------
# MEI018 — FeedbackSubmission flips to COMPLETED
# ---------------------------------------------------------------------------

def test_feedback_submission_marks_dispatch_completed(record):
    dispatch_post_incubation_survey(record=record, member=record.business)
    row = SMEHubBeneficiaryFeedback.objects.get(tracking_record=record)
    channel = row.feedback_channel
    assert channel is not None

    submission = FeedbackSubmission.objects.create(
        channel=channel, narrative="Great experience.",
        rating=5, severity=FeedbackSeverity.LOW,
    )
    fresh = SMEHubBeneficiaryFeedback.objects.get(pk=row.pk)
    assert fresh.status == SMEHubBeneficiaryFeedback.Status.COMPLETED
    assert fresh.completed_at is not None
    assert fresh.feedback_submission_id == submission.pk


def test_mark_completed_idempotent_when_already_completed(record):
    dispatch_post_incubation_survey(record=record, member=record.business)
    row = SMEHubBeneficiaryFeedback.objects.get(tracking_record=record)
    channel = row.feedback_channel
    first = FeedbackSubmission.objects.create(
        channel=channel, narrative="A", severity=FeedbackSeverity.LOW,
    )
    second = FeedbackSubmission.objects.create(
        channel=channel, narrative="B", severity=FeedbackSeverity.LOW,
    )
    fresh = SMEHubBeneficiaryFeedback.objects.get(pk=row.pk)
    # Status remains COMPLETED; the second submission is ignored for the
    # dispatch row (only SENT rows transition to COMPLETED).
    assert fresh.status == SMEHubBeneficiaryFeedback.Status.COMPLETED
    assert fresh.feedback_submission_id == first.pk


# ---------------------------------------------------------------------------
# MEI019 — reminders + non-response sweep
# ---------------------------------------------------------------------------

def test_reminder_task_notifies_overdue_unreminded(record):
    dispatch_post_incubation_survey(record=record, member=record.business)
    row = SMEHubBeneficiaryFeedback.objects.get(tracking_record=record)
    # Backdate dispatch so the reminder threshold trips.
    SMEHubBeneficiaryFeedback.objects.filter(pk=row.pk).update(
        dispatched_at=timezone.now() - timedelta(days=10),
    )

    sent = dispatch_feedback_reminders_task()
    assert sent == 1
    assert SMEHubBeneficiaryFeedback.objects.get(pk=row.pk).reminded_at is not None


def test_reminder_task_does_not_remind_twice(record):
    dispatch_post_incubation_survey(record=record, member=record.business)
    row = SMEHubBeneficiaryFeedback.objects.get(tracking_record=record)
    SMEHubBeneficiaryFeedback.objects.filter(pk=row.pk).update(
        dispatched_at=timezone.now() - timedelta(days=10),
    )
    dispatch_feedback_reminders_task()
    second = dispatch_feedback_reminders_task()
    assert second == 0


def test_reminder_task_skips_completed_rows(record):
    dispatch_post_incubation_survey(record=record, member=record.business)
    row = SMEHubBeneficiaryFeedback.objects.get(tracking_record=record)
    FeedbackSubmission.objects.create(
        channel=row.feedback_channel, narrative="done",
        severity=FeedbackSeverity.LOW,
    )
    SMEHubBeneficiaryFeedback.objects.filter(pk=row.pk).update(
        dispatched_at=timezone.now() - timedelta(days=10),
    )
    sent = dispatch_feedback_reminders_task()
    assert sent == 0


def test_non_response_task_flags_overdue_and_bumps_counter(record):
    dispatch_post_incubation_survey(record=record, member=record.business)
    row = SMEHubBeneficiaryFeedback.objects.get(tracking_record=record)
    # Roll the due_at into the past.
    SMEHubBeneficiaryFeedback.objects.filter(pk=row.pk).update(
        due_at=timezone.now() - timedelta(hours=1),
    )

    flagged = record_feedback_non_responses_task()
    assert flagged == 1
    fresh = SMEHubBeneficiaryFeedback.objects.get(pk=row.pk)
    assert fresh.status == SMEHubBeneficiaryFeedback.Status.NON_RESPONDED
    refreshed_record = SMEHubTrackingRecord.objects.get(pk=record.pk)
    assert refreshed_record.feedback_non_response_count == 1


def test_non_response_task_skips_pre_due_dispatches(record):
    """Surveys whose due_at is still in the future stay SENT."""
    dispatch_post_incubation_survey(record=record, member=record.business)
    flagged = record_feedback_non_responses_task()
    assert flagged == 0
    row = SMEHubBeneficiaryFeedback.objects.get(tracking_record=record)
    assert row.status == SMEHubBeneficiaryFeedback.Status.SENT
