"""Tests for SME-Hub onboarding services (SP1).

Coverage targets the key PRD acceptance points:
* FRSME-OBR002 / OBR009 — registration record persists on submit and resend
  re-uses it without losing data.
* FRSME-OBR011/A2 — RIMS match lookup returns matches when present and
  reports no-match cleanly.
* FRSME-OBR017 / OBR018 / OBR019 / OBR020 — verify, reject, more-info,
  bulk verify flows transition status correctly.
* FRSME-OBR021 / OBR025 — only verified entrepreneurs may register, and
  multiple businesses per entrepreneur are supported.
* FRSME-OBR029 — revoking a verified business records reason.
* FRSME-OBR033 / OBR035 — multi-affiliation with primary swap; baseline
  locked exactly once even if more affiliations are added.
"""
from __future__ import annotations

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

from apps.core.authentication.models import Institution
from apps.smehub.onboarding import services, signals
from apps.smehub.onboarding.models import (
    AIHAffiliation,
    Business,
    BusinessBaseline,
    BusinessStage,
    EntrepreneurProfile,
    RegistrationRecord,
    RevenueRange,
)

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


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

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


@pytest.fixture
def institution():
    return Institution.objects.create(name="Test University", country="UG", is_member=True, is_active=True)


@pytest.fixture
def verified_entrepreneur(admin_user):
    record = services.register_user(
        full_name="Sample Entrepreneur",
        email="sample@example.com",
        password="StrongP@ss123",
        country="UG",
        organisation="Test Co",
        phone="+256000000",
        role=RegistrationRecord.Role.ENTREPRENEUR,
    )
    user = services.verify_email(token=record.token)
    profile = services.submit_entrepreneur_profile(
        user=user,
        country="UG",
        organisation="Test Co",
        phone="+256000000",
        bio="Testing.",
    )
    services.verify_entrepreneur(profile, by_user=admin_user)
    return profile


# ---------------------------------------------------------------------------
# Registration & email verification
# ---------------------------------------------------------------------------

def test_register_user_persists_record_immediately():
    record = services.register_user(
        full_name="Asha Ndlovu",
        email="asha@example.com",
        password="StrongP@ss123",
        country="ZW",
        organisation="Bulawayo AgriCo",
        phone="+263000000",
        role=RegistrationRecord.Role.ENTREPRENEUR,
    )
    assert RegistrationRecord.objects.filter(pk=record.pk).exists()
    assert record.consumed_at is None
    assert User.objects.filter(email="asha@example.com").count() == 1
    user = User.objects.get(email="asha@example.com")
    assert user.is_active is False  # not yet verified


def test_register_user_blocks_duplicate_active_email():
    User.objects.create_user(email="dup@example.com", password="x", is_active=True)
    with pytest.raises(services.DuplicateEmailError):
        services.register_user(
            full_name="x",
            email="dup@example.com",
            password="StrongP@ss123",
            country="",
            organisation="",
            phone="",
            role=RegistrationRecord.Role.ENTREPRENEUR,
        )


def test_resend_verification_reuses_persisted_data():
    record = services.register_user(
        full_name="Boni Wanyama",
        email="boni@example.com",
        password="StrongP@ss123",
        country="UG",
        organisation="GreenSeed",
        phone="",
        role=RegistrationRecord.Role.ENTREPRENEUR,
    )
    original_token = record.token
    refreshed = services.resend_verification(email="boni@example.com")
    assert refreshed is not None
    assert refreshed.pk == record.pk
    assert refreshed.token != original_token  # rotated
    assert refreshed.full_name == "Boni Wanyama"  # data preserved


def test_verify_email_activates_user_and_creates_role_profile():
    record = services.register_user(
        full_name="Geoffrey Mentor",
        email="geo@example.com",
        password="StrongP@ss123",
        country="KE",
        organisation="Hub",
        phone="",
        role=RegistrationRecord.Role.MENTOR,
    )
    user = services.verify_email(token=record.token)
    assert user.is_active is True
    assert user.email_verified is True
    assert user.role == "mentor"
    assert hasattr(user, "+_smehub_mentor_profile") is False  # accessor name not relevant; existence below
    from apps.smehub.onboarding.models import MentorProfile
    assert MentorProfile.objects.filter(user=user).exists()


def test_verify_email_rejects_expired_token():
    record = services.register_user(
        full_name="x", email="exp@example.com", password="StrongP@ss123",
        country="", organisation="", phone="",
        role=RegistrationRecord.Role.ENTREPRENEUR,
    )
    record.token_expires_at = timezone.now() - timezone.timedelta(seconds=1) if hasattr(timezone, "timedelta") else record.token_expires_at
    # Use stdlib timedelta
    from datetime import timedelta
    record.token_expires_at = timezone.now() - timedelta(seconds=1)
    record.save(update_fields=["token_expires_at"])
    with pytest.raises(services.TokenInvalidError):
        services.verify_email(token=record.token)


# ---------------------------------------------------------------------------
# RIMS match lookup
# ---------------------------------------------------------------------------

def test_lookup_rims_match_no_records_returns_empty_lists():
    out = services.lookup_rims_match("nobody@example.com")
    assert out == {"applications": [], "scholars": []}


# ---------------------------------------------------------------------------
# Verification flows
# ---------------------------------------------------------------------------

def test_verify_entrepreneur_transitions_status(admin_user):
    user = User.objects.create_user(email="ent@example.com", password="x", role="entrepreneur")
    profile = services.submit_entrepreneur_profile(user=user)
    services.verify_entrepreneur(profile, by_user=admin_user)
    profile = EntrepreneurProfile.objects.get(pk=profile.pk)
    assert profile.verification_status == EntrepreneurProfile.Status.VERIFIED
    assert profile.verified_by == admin_user


def test_reject_entrepreneur_requires_reason(admin_user):
    user = User.objects.create_user(email="ent@example.com", password="x", role="entrepreneur")
    profile = services.submit_entrepreneur_profile(user=user)
    with pytest.raises(ValueError):
        services.reject_entrepreneur(profile, by_user=admin_user, reason="   ")
    services.reject_entrepreneur(profile, by_user=admin_user, reason="incomplete docs")
    profile = EntrepreneurProfile.objects.get(pk=profile.pk)
    assert profile.verification_status == EntrepreneurProfile.Status.REJECTED
    assert "incomplete" in profile.rejection_reason


def test_request_more_info_flag_persists(admin_user):
    user = User.objects.create_user(email="ent@example.com", password="x", role="entrepreneur")
    profile = services.submit_entrepreneur_profile(user=user)
    services.request_more_info_from_entrepreneur(
        profile, by_user=admin_user, message="please upload ID",
    )
    profile = EntrepreneurProfile.objects.get(pk=profile.pk)
    assert profile.verification_status == EntrepreneurProfile.Status.PENDING_MORE_INFO
    assert "please upload ID" in profile.info_request_message


def test_bulk_verify_entrepreneurs(admin_user):
    profile_ids = []
    for i in range(3):
        u = User.objects.create_user(email=f"b{i}@example.com", password="x", role="entrepreneur")
        p = services.submit_entrepreneur_profile(user=u)
        profile_ids.append(p.pk)
    count = services.bulk_verify_entrepreneurs(profile_ids, by_user=admin_user)
    assert count == 3
    assert (
        EntrepreneurProfile.objects.filter(
            pk__in=profile_ids, verification_status=EntrepreneurProfile.Status.VERIFIED,
        ).count()
        == 3
    )


# ---------------------------------------------------------------------------
# Business + baseline + affiliation
# ---------------------------------------------------------------------------

def test_business_registration_blocked_for_unverified_entrepreneur():
    user = User.objects.create_user(email="np@example.com", password="x", role="entrepreneur")
    profile = services.submit_entrepreneur_profile(user=user)
    with pytest.raises(services.EntrepreneurNotVerifiedError):
        services.register_business(
            entrepreneur=profile, name="X", sector="Agri",
            business_stage=BusinessStage.IDEA,
        )


def test_business_registration_creates_baseline(verified_entrepreneur):
    biz = services.register_business(
        entrepreneur=verified_entrepreneur,
        name="Pilot Co",
        sector="Agriculture",
        business_stage=BusinessStage.MVP,
        fte_count=3,
        pte_count=1,
        revenue_range=RevenueRange.UNDER_1K,
    )
    assert biz.business_id.startswith("SME-")
    assert biz.verification_status == Business.Status.PENDING_ADMIN_REVIEW
    baseline = BusinessBaseline.objects.get(business=biz)
    assert baseline.fte_count == 3
    assert baseline.locked_at is None  # locks on first affiliation


def test_multi_business_per_entrepreneur(verified_entrepreneur):
    services.register_business(
        entrepreneur=verified_entrepreneur, name="A", sector="Agri",
        business_stage=BusinessStage.IDEA,
    )
    services.register_business(
        entrepreneur=verified_entrepreneur, name="B", sector="Energy",
        business_stage=BusinessStage.MVP,
    )
    assert verified_entrepreneur.businesses.count() == 2
    assert (
        verified_entrepreneur.businesses.values_list("business_id", flat=True).distinct().count() == 2
    )


def test_baseline_signal_emits_once_on_first_affiliation(
    verified_entrepreneur, institution, admin_user,
):
    biz = services.register_business(
        entrepreneur=verified_entrepreneur, name="Q", sector="Agri",
        business_stage=BusinessStage.IDEA, fte_count=2, pte_count=0,
        revenue_range=RevenueRange.NONE,
    )
    services.verify_business(biz, by_user=admin_user)

    received: list[dict] = []

    def _handler(sender, business, baseline, entrepreneur, **kwargs):
        received.append({"business": business.pk, "baseline": baseline.pk})

    signals.smehub_baseline_initialised.connect(_handler)
    try:
        services.record_aih_affiliation(
            business=biz, institution_id=institution.pk,
            nature=AIHAffiliation.AffiliationNature.STUDENT, is_primary=True,
        )
        # Adding a second non-primary affiliation must not re-emit the baseline signal.
        institution2 = Institution.objects.create(
            name="Other Uni", country="UG", is_member=True, is_active=True,
        )
        services.record_aih_affiliation(
            business=biz, institution_id=institution2.pk,
            nature=AIHAffiliation.AffiliationNature.ALUMNI, is_primary=False,
        )
    finally:
        signals.smehub_baseline_initialised.disconnect(_handler)

    assert len(received) == 1, f"baseline signal should fire exactly once, got {received}"
    biz = Business.objects.get(pk=biz.pk)
    assert biz.baseline.locked_at is not None
    assert biz.affiliations.count() == 2


def test_primary_affiliation_swap_demotes_previous(verified_entrepreneur, institution, admin_user):
    biz = services.register_business(
        entrepreneur=verified_entrepreneur, name="R", sector="Agri",
        business_stage=BusinessStage.IDEA,
    )
    services.verify_business(biz, by_user=admin_user)
    institution2 = Institution.objects.create(
        name="Second Uni", country="KE", is_member=True, is_active=True,
    )
    services.record_aih_affiliation(
        business=biz, institution_id=institution.pk,
        nature=AIHAffiliation.AffiliationNature.STUDENT, is_primary=True,
    )
    services.record_aih_affiliation(
        business=biz, institution_id=institution2.pk,
        nature=AIHAffiliation.AffiliationNature.ALUMNI, is_primary=True,
    )
    primaries = AIHAffiliation.objects.filter(business=biz, is_primary=True)
    assert primaries.count() == 1
    assert primaries.first().institution_id == institution2.pk


def test_admin_set_aih_affiliation_creates_with_admin_flag(
    verified_entrepreneur, institution, admin_user,
):
    """FRSME-OBR034 — admin sets an affiliation; stored with set_by_admin + audit."""
    from apps.smehub._shared.audit import AuditLog

    biz = services.register_business(
        entrepreneur=verified_entrepreneur, name="AdminAff", sector="Agri",
        business_stage=BusinessStage.IDEA,
    )
    services.verify_business(biz, by_user=admin_user)
    aff = services.set_aih_affiliation_by_admin(
        business=biz,
        institution_id=institution.pk,
        nature=AIHAffiliation.AffiliationNature.STUDENT,
        is_primary=True,
        admin=admin_user,
    )
    assert aff.set_by_admin is True
    assert aff.institution_id == institution.pk
    assert AuditLog.objects.filter(actor=admin_user, action=AuditLog.Action.CREATE).exists()


def test_admin_correct_aih_affiliation_updates_and_audits(
    verified_entrepreneur, institution, admin_user,
):
    """Re-submitting the same institution updates in place + writes a 2nd audit."""
    from apps.smehub._shared.audit import AuditLog

    biz = services.register_business(
        entrepreneur=verified_entrepreneur, name="AdminAff2", sector="Agri",
        business_stage=BusinessStage.IDEA,
    )
    services.verify_business(biz, by_user=admin_user)
    services.set_aih_affiliation_by_admin(
        business=biz, institution_id=institution.pk,
        nature=AIHAffiliation.AffiliationNature.STUDENT, is_primary=True, admin=admin_user,
    )
    # Correct to a different nature on the same institution → update, not a new row.
    aff = services.set_aih_affiliation_by_admin(
        business=biz, institution_id=institution.pk,
        nature=AIHAffiliation.AffiliationNature.ALUMNI, is_primary=True, admin=admin_user,
    )
    assert AIHAffiliation.objects.filter(business=biz).count() == 1
    assert aff.nature == AIHAffiliation.AffiliationNature.ALUMNI
    assert AuditLog.objects.filter(actor=admin_user, action=AuditLog.Action.UPDATE).exists()


def test_revoke_business_requires_reason(verified_entrepreneur, admin_user):
    biz = services.register_business(
        entrepreneur=verified_entrepreneur, name="S", sector="Agri",
        business_stage=BusinessStage.IDEA,
    )
    services.verify_business(biz, by_user=admin_user)
    with pytest.raises(ValueError):
        services.revoke_business(biz, by_user=admin_user, reason="")
    services.revoke_business(biz, by_user=admin_user, reason="closed shop")
    biz = Business.objects.get(pk=biz.pk)
    assert biz.verification_status == Business.Status.REVOKED
    assert "closed shop" in biz.revoke_reason
