"""Integration test — the new shared helpers must produce the same
weighted-average answer as SP2's existing ``compute_weighted_average``
when both run against identical SP2 model instances.

This guards Phase 3: when the SP2 service swaps its bespoke helper for
``apps.smehub._shared.calls.compute_weighted_average``, the ranking
output must be byte-identical (modulo the new ``require_all_sections``
opt-in behaviour).
"""
from __future__ import annotations

from datetime import timedelta
from decimal import Decimal

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

from apps.core.permissions.roles import UserRole
from apps.smehub._shared.calls import compute_weighted_average
from apps.smehub.incubation import services as inc_services
from apps.smehub.incubation.models import (
    Application,
    JudgeScore,
    Programme,
)
from apps.smehub.onboarding import signals as onb_signals
from apps.smehub.onboarding.models import (
    Business,
    BusinessBaseline,
    BusinessStage,
    EntrepreneurProfile,
    RevenueRange,
)

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


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


@pytest.fixture
def officer():
    return User.objects.create_user(
        email="po-shared@example.com", password="x", role=UserRole.PROGRAMME_OFFICER,
    )


@pytest.fixture
def verified_business(admin):
    user = User.objects.create_user(
        email="ent-shared@example.com", password="x", role=UserRole.ENTREPRENEUR,
    )
    profile = EntrepreneurProfile.objects.create(
        user=user, country="UG", organisation="Test U", phone="+256000",
    )
    profile.verify(by_user=user)
    profile.save()

    biz = Business.objects.create(
        entrepreneur=profile,
        name="Shared Co",
        sector="Agriculture",
        sub_sector="Maize",
        business_stage=BusinessStage.MVP,
        country="UG",
    )
    baseline = BusinessBaseline.objects.create(
        business=biz,
        business_stage_at_entry=BusinessStage.MVP,
        fte_count=2,
        pte_count=1,
        revenue_range=RevenueRange.UNDER_1K,
        primary_target_market="Local",
        geographic_reach="UG",
    )
    biz.verify(by_user=admin)
    biz.save()
    onb_signals.smehub_baseline_initialised.send(
        sender=BusinessBaseline,
        business=biz,
        baseline=baseline,
        entrepreneur=profile,
    )
    return biz


@pytest.fixture
def published_programme(officer):
    p = inc_services.create_programme(
        officer=officer,
        title="Shared Helper Programme",
        slug="shared-helper-prog",
        objectives="Test rubric reuse.",
        target_sectors=["Agriculture"],
        target_stages=["mvp", "early_revenue"],
        cohort_size=5,
        duration_weeks=10,
        application_deadline=timezone.now() + timedelta(days=30),
    )
    inc_services.add_rubric_section(p, title="Innovation", weight=Decimal("0.7"), max_marks=10)
    inc_services.add_rubric_section(p, title="Market", weight=Decimal("0.3"), max_marks=10)
    p.judges.set([User.objects.create_user(email="sp2int-judge@example.com", password="x", role=UserRole.JUDGE)])
    inc_services.publish_programme(p, by_user=officer)
    return Programme.objects.get(pk=p.pk)


@pytest.fixture
def submitted_application(published_programme, verified_business):
    entrepreneur = verified_business.entrepreneur
    app = inc_services.start_application(
        entrepreneur=entrepreneur,
        business=verified_business,
        programme=published_programme,
    )
    app.business_description = "Shared Co grows and processes maize for local markets."
    app.problem_statement = "Smallholders lack access to affordable processing capacity."
    app.save()
    inc_services.submit_application(app)
    return Application.objects.get(pk=app.pk)


@pytest.fixture
def judges():
    return [
        User.objects.create_user(
            email=f"jshared{i}@example.com", password="x", role=UserRole.JUDGE,
        )
        for i in range(2)
    ]


def test_shared_helper_matches_sp2_helper_for_full_submission(
    submitted_application, published_programme, judges,
):
    """When every judge has scored every section, the shared helper and
    the SP2 helper must agree exactly."""
    rubric = published_programme.rubric
    s1, s2 = list(rubric.sections.order_by("order"))
    j1, j2 = judges
    JudgeScore.objects.create(application=submitted_application, judge=j1, section=s1, score=Decimal("8"))
    JudgeScore.objects.create(application=submitted_application, judge=j2, section=s1, score=Decimal("6"))
    JudgeScore.objects.create(application=submitted_application, judge=j1, section=s2, score=Decimal("7"))
    JudgeScore.objects.create(application=submitted_application, judge=j2, section=s2, score=Decimal("5"))

    sp2_value = inc_services.compute_weighted_average(submitted_application)
    shared_value = compute_weighted_average(
        sections=rubric.sections.all(),
        scores=JudgeScore.objects.filter(application=submitted_application),
    )

    assert sp2_value == shared_value


def test_shared_helper_returns_none_when_partial_and_strict(
    submitted_application, published_programme, judges,
):
    """The new behaviour: ``require_all_sections=True`` must defer ranking."""
    rubric = published_programme.rubric
    s1, _s2 = list(rubric.sections.order_by("order"))
    j1, _j2 = judges
    # Only section 1 scored
    JudgeScore.objects.create(application=submitted_application, judge=j1, section=s1, score=Decimal("8"))

    strict = compute_weighted_average(
        sections=rubric.sections.all(),
        scores=JudgeScore.objects.filter(application=submitted_application),
        require_all_sections=True,
    )
    lenient = compute_weighted_average(
        sections=rubric.sections.all(),
        scores=JudgeScore.objects.filter(application=submitted_application),
    )
    assert strict is None
    # Lenient mode reproduces SP2's silent partial average for backwards compat.
    assert lenient is not None
    sp2_value = inc_services.compute_weighted_average(submitted_application)
    assert lenient == sp2_value
