"""SP6 — SME-Hub × M&EL wiring tests (FRSME-MEI001 → MEI022).

Coverage targets:

  • baseline_initialised creates an SMEHubTrackingRecord (MEI001)
  • SP2 baseline guard blocks start_application without a record (MEI002)
  • SP2/SP3/SP4/SP5 receivers append milestones with last_milestone_at
    bumped (MEI003 → MEI006)
  • feed-handler logs failures without bubbling (MEI007)
  • stall detection flags inactive records past threshold (MEI012)
  • backfill_smehub_tracking is idempotent
  • donor + learning report builders produce structured rollups (MEI014–020)
  • end-to-end longitudinal trail from SP1 baseline → SP5 funding_secured
"""
from __future__ import annotations

from datetime import timedelta
from decimal import Decimal
from unittest import mock

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.tracking.models import (
    SMEHubBeneficiaryFeedback,
    SMEHubFeedHandlerLog,
    SMEHubTrackingRecord,
)
from apps.smehub.onboarding.models import (
    AIHAffiliation,
    Business,
    BusinessBaseline,
    BusinessStage,
    EntrepreneurProfile,
)
from apps.smehub.onboarding import signals as onb_signals

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


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def admin_user():
    return User.objects.create_user(
        email="sp6-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="sp6-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="AgriCo Sp6",
        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 initialised_record(entrepreneur, business, baseline):
    """Fire the baseline signal so SP6 receivers create a tracking record."""
    onb_signals.smehub_baseline_initialised.send(
        sender=BusinessBaseline,
        business=business,
        baseline=baseline,
        entrepreneur=entrepreneur,
    )
    return SMEHubTrackingRecord.objects.get(entrepreneur=entrepreneur)


# ---------------------------------------------------------------------------
# FRSME-MEI001 — baseline_initialised creates the tracking record
# ---------------------------------------------------------------------------

def test_baseline_initialised_creates_tracking_record(entrepreneur, business, baseline):
    onb_signals.smehub_baseline_initialised.send(
        sender=BusinessBaseline,
        business=business,
        baseline=baseline,
        entrepreneur=entrepreneur,
    )
    record = SMEHubTrackingRecord.objects.get(entrepreneur=entrepreneur)
    assert record.sp1_baseline_at is not None
    assert record.business_id == business.pk
    assert record.baseline_snapshot_id == baseline.pk


def test_baseline_initialised_is_idempotent(entrepreneur, business, baseline):
    onb_signals.smehub_baseline_initialised.send(
        sender=BusinessBaseline,
        business=business,
        baseline=baseline,
        entrepreneur=entrepreneur,
    )
    onb_signals.smehub_baseline_initialised.send(
        sender=BusinessBaseline,
        business=business,
        baseline=baseline,
        entrepreneur=entrepreneur,
    )
    assert SMEHubTrackingRecord.objects.filter(entrepreneur=entrepreneur).count() == 1


# ---------------------------------------------------------------------------
# FRSME-MEI002 — SP2 baseline guard
# ---------------------------------------------------------------------------

def _make_published_programme(slug: str, admin_user) -> "Programme":
    from apps.smehub.incubation.models import (
        Programme,
        RubricSection,
        ScoringRubric,
    )
    from apps.smehub.incubation.services import publish_programme

    programme = Programme.objects.create(
        title=f"Test {slug}",
        slug=slug,
        cohort_size=10,
        application_deadline=timezone.now() + timedelta(days=10),
    )
    rubric = ScoringRubric.objects.create(programme=programme)
    RubricSection.objects.create(
        rubric=rubric,
        title="Pitch",
        weight=Decimal("1.0000"),
        order=1,
    )
    programme.judges.set([
        User.objects.create_user(
            email=f"mel-roster-judge-{slug}@example.com", password="x", role=UserRole.JUDGE,
        ),
    ])
    publish_programme(programme, by_user=admin_user)
    return programme


def test_start_application_requires_baseline(entrepreneur, business, admin_user):
    from apps.smehub.incubation.services import IncubationStateError, start_application

    programme = _make_published_programme("prog-test-noguard", admin_user)

    with pytest.raises(IncubationStateError):
        start_application(
            entrepreneur=entrepreneur,
            business=business,
            programme=programme,
        )


def test_start_application_succeeds_after_baseline(initialised_record, entrepreneur, business, admin_user):
    """With a tracking record present, start_application should pass the SP6
    guard and proceed to its existing eligibility checks (creating the row)."""
    from apps.smehub.incubation.services import start_application

    programme = _make_published_programme("prog-test-open", admin_user)

    application = start_application(
        entrepreneur=entrepreneur,
        business=business,
        programme=programme,
    )
    assert application.pk is not None


# ---------------------------------------------------------------------------
# FRSME-MEI003 — SP2 milestones append
# ---------------------------------------------------------------------------

def test_application_submitted_appends_milestone(initialised_record, business, admin_user):
    from apps.smehub.incubation.services import (
        start_application,
        submit_application,
    )

    programme = _make_published_programme("prog-mei003", admin_user)
    application = start_application(
        entrepreneur=initialised_record.entrepreneur,
        business=business,
        programme=programme,
    )
    application.business_description = "Filled enough to submit."
    application.problem_statement = "Filled enough to submit."
    application.traction = "Filled."
    application.funding_needs = "Filled."
    application.support_sought = "Filled."
    application.save()
    submit_application(application)

    record = SMEHubTrackingRecord.objects.get(pk=initialised_record.pk)
    keys = {entry["event"] for entry in record.sp2_milestones}
    assert "application_submitted" in keys
    assert record.last_milestone_at is not None


# ---------------------------------------------------------------------------
# FRSME-MEI006 — funding_secured bumps total + signal-driven append
# ---------------------------------------------------------------------------

def test_funding_secured_bumps_running_total(initialised_record, entrepreneur, business):
    from apps.smehub.investment import signals as inv_signals

    inv_signals.smehub_funding_secured.send(
        sender=None,
        entrepreneur=entrepreneur,
        business=business,
        amount=Decimal("12500"),
        source="application",
    )
    record = SMEHubTrackingRecord.objects.get(pk=initialised_record.pk)
    assert record.funding_secured_total == Decimal("12500")
    keys = {entry["event"] for entry in record.sp5_investment}
    assert "funding_secured" in keys


# ---------------------------------------------------------------------------
# FRSME-MEI007 — feed-handler logs failures without bubbling
# ---------------------------------------------------------------------------

def test_feed_handler_logs_failure(monkeypatch, initialised_record, entrepreneur, business):
    """Force append_milestone to blow up — the receiver must catch and log."""

    def boom(self, *args, **kwargs):
        raise RuntimeError("forced")

    monkeypatch.setattr(SMEHubTrackingRecord, "append_milestone", boom)

    from apps.smehub.investment import signals as inv_signals

    # Should not raise — receiver swallows + logs to SMEHubFeedHandlerLog.
    inv_signals.smehub_funding_secured.send(
        sender=None,
        entrepreneur=entrepreneur,
        business=business,
        amount=Decimal("5"),
        source="manual",
    )
    assert SMEHubFeedHandlerLog.objects.filter(
        signal_name="smehub_funding_secured",
    ).exists()


# ---------------------------------------------------------------------------
# FRSME-MEI012 — stall detection
# ---------------------------------------------------------------------------

def test_stall_task_flags_inactive_records(initialised_record):
    # Push last_milestone_at well past the threshold.
    SMEHubTrackingRecord.objects.filter(pk=initialised_record.pk).update(
        last_milestone_at=timezone.now() - timedelta(days=200),
    )
    from apps.mel.tracking.tasks import flag_stalled_smehub_records_task

    flagged = flag_stalled_smehub_records_task(stall_threshold_days=90)
    record = SMEHubTrackingRecord.objects.get(pk=initialised_record.pk)
    assert flagged == 1
    assert record.is_stalled
    assert record.stall_flagged_at is not None


def test_stall_task_skips_recent_records(initialised_record):
    SMEHubTrackingRecord.objects.filter(pk=initialised_record.pk).update(
        last_milestone_at=timezone.now() - timedelta(days=10),
    )
    from apps.mel.tracking.tasks import flag_stalled_smehub_records_task

    flagged = flag_stalled_smehub_records_task(stall_threshold_days=90)
    assert flagged == 0


def test_new_milestone_clears_stall_flag(initialised_record, entrepreneur, business):
    """A fresh signal on a stalled record should re-activate it."""
    SMEHubTrackingRecord.objects.filter(pk=initialised_record.pk).update(
        is_stalled=True,
        stall_flagged_at=timezone.now() - timedelta(days=5),
        last_milestone_at=timezone.now() - timedelta(days=200),
    )
    from apps.smehub.investment import signals as inv_signals

    inv_signals.smehub_funding_secured.send(
        sender=None,
        entrepreneur=entrepreneur,
        business=business,
        amount=Decimal("100"),
        source="manual",
    )
    record = SMEHubTrackingRecord.objects.get(pk=initialised_record.pk)
    assert not record.is_stalled
    assert record.stall_flagged_at is None


# ---------------------------------------------------------------------------
# Backfill management command
# ---------------------------------------------------------------------------

def test_backfill_is_idempotent(entrepreneur, business, baseline):
    # Lock the baseline so the backfill picks it up.
    baseline.locked_at = timezone.now()
    baseline.save(update_fields=["locked_at"])
    from django.core.management import call_command

    call_command("backfill_smehub_tracking", verbosity=0)
    first_count = SMEHubTrackingRecord.objects.filter(entrepreneur=entrepreneur).count()
    assert first_count == 1
    record = SMEHubTrackingRecord.objects.get(entrepreneur=entrepreneur)
    sp2_first = list(record.sp2_milestones)

    call_command("backfill_smehub_tracking", verbosity=0)
    record = SMEHubTrackingRecord.objects.get(entrepreneur=entrepreneur)
    assert SMEHubTrackingRecord.objects.filter(entrepreneur=entrepreneur).count() == 1
    assert list(record.sp2_milestones) == sp2_first


# ---------------------------------------------------------------------------
# Report builders (FRSME-MEI014, MEI020, MEI021)
# ---------------------------------------------------------------------------

def test_donor_report_builder_returns_structured_rollup(initialised_record, entrepreneur, business):
    from apps.smehub.investment import signals as inv_signals
    from apps.mel.reports.builders import build_smehub_donor_report

    inv_signals.smehub_funding_secured.send(
        sender=None,
        entrepreneur=entrepreneur,
        business=business,
        amount=Decimal("9000"),
        source="manual",
    )
    rollup = build_smehub_donor_report()
    assert "totals" in rollup
    assert "by_sector" in rollup
    assert "by_aih" in rollup
    assert "rims_links" in rollup


def test_learning_report_builder_runs_without_errors(initialised_record):
    from apps.mel.reports.builders import build_smehub_learning_report

    rollup = build_smehub_learning_report()
    assert "sector_performance" in rollup
    assert "aih_completion" in rollup
    assert "partnership_to_deal" in rollup


# ---------------------------------------------------------------------------
# End-to-end longitudinal trail (smoke)
# ---------------------------------------------------------------------------

def test_end_to_end_signals_populate_all_buckets(initialised_record, entrepreneur, business):
    """Fire one canonical signal per phase and assert each bucket grew."""
    from apps.smehub.investment import signals as inv_signals

    # SP5 funding_secured (manual)
    inv_signals.smehub_funding_secured.send(
        sender=None,
        entrepreneur=entrepreneur,
        business=business,
        amount=Decimal("1000"),
        source="manual",
    )

    record = SMEHubTrackingRecord.objects.get(pk=initialised_record.pk)
    assert record.sp1_baseline_at is not None
    assert any(e["event"] == "funding_secured" for e in record.sp5_investment)
    assert record.last_milestone_at >= record.sp1_baseline_at
