"""Phase 0 foundation tests.

Locks the contract for the cross-cutting helpers that every later SME-Hub
phase depends on:

* ``apps.smehub._shared.audit._audit`` writes one ``AuditLog`` row per call,
  with the right shape, and accepts a ``None`` target without crashing.
* ``apps.smehub._shared.role_gates.MELOfficerRequired`` grants access to
  M&E officers and SME admins, and denies everyone else (regression guard
  for the SP6 audit finding).
* ``apps.smehub._shared.notifications.smehub_verbs`` returns the canonical
  inventory of SME-Hub verbs.
* The three Phase 0 verbs (``smehub_entrepreneur_pending_review``,
  ``smehub_new_tracking_record``, ``smehub_role_profile_verified``) exist on
  ``Notification.Verb`` so any later phase that wires them up does not
  silently fall back to GENERIC.
"""
from __future__ import annotations

import pytest
from django.contrib.auth import get_user_model
from django.http import HttpResponse
from django.test import RequestFactory
from django.views.generic import View

from apps.core.audit.models import AuditLog
from apps.core.notifications.models import Notification
from apps.core.permissions.roles import UserRole
from apps.smehub._shared.audit import _audit, record_audit
from apps.smehub._shared.notifications import smehub_verbs
from apps.smehub._shared.role_gates import MELOfficerRequired

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


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

@pytest.fixture
def user_factory():
    counter = {"n": 0}

    def make(role: str = UserRole.ADMIN, **kwargs):
        counter["n"] += 1
        return User.objects.create_user(
            email=f"phase0-{counter['n']}@example.com",
            password="x",
            role=role,
            **kwargs,
        )

    return make


# ---------------------------------------------------------------------------
# _audit
# ---------------------------------------------------------------------------

def test_audit_writes_one_log_row_with_metadata(user_factory):
    actor = user_factory()
    target = user_factory(role=UserRole.ENTREPRENEUR)

    log = record_audit(actor, AuditLog.Action.UPDATE, target, metadata={"event": "test"})

    assert log is not None
    assert log.actor_id == actor.pk
    assert log.action == AuditLog.Action.UPDATE
    assert log.target_app == target._meta.app_label
    assert log.target_model == target._meta.model_name
    assert log.object_id == str(target.pk)
    assert log.changes == {"event": "test"}


def test_audit_returns_none_for_missing_target():
    assert record_audit(actor=None, action="UPDATE", target=None) is None
    assert AuditLog.objects.count() == 0


def test_audit_alias_matches_record_audit():
    """The legacy ``_audit`` symbol must resolve to ``record_audit``."""
    assert _audit is record_audit


def test_audit_drops_unauthenticated_actor(user_factory):
    """A request without an authenticated user should not pollute actor_id."""
    target = user_factory(role=UserRole.ENTREPRENEUR)
    log = record_audit(actor=None, action="CREATE", target=target)
    assert log is not None
    assert log.actor_id is None


# ---------------------------------------------------------------------------
# MELOfficerRequired
# ---------------------------------------------------------------------------

class _OkView(MELOfficerRequired, View):
    raise_exception = True

    def get(self, request, *args, **kwargs):
        return HttpResponse("ok")


@pytest.mark.parametrize(
    "role",
    [UserRole.MEL_OFFICER, UserRole.SME_ADMIN, UserRole.ADMIN, UserRole.SYSTEM_ADMIN],
)
def test_mel_officer_required_grants_intended_roles(user_factory, role):
    user = user_factory(role=role)
    request = RequestFactory().get("/")
    request.user = user
    response = _OkView.as_view()(request)
    assert response.status_code == 200


@pytest.mark.parametrize(
    "role",
    [UserRole.ENTREPRENEUR, UserRole.MENTOR, UserRole.INVESTOR, UserRole.JUDGE],
)
def test_mel_officer_required_denies_other_roles(user_factory, role):
    from django.core.exceptions import PermissionDenied

    user = user_factory(role=role)
    request = RequestFactory().get("/")
    request.user = user
    with pytest.raises(PermissionDenied):
        _OkView.as_view()(request)


# ---------------------------------------------------------------------------
# Verb inventory
# ---------------------------------------------------------------------------

PHASE0_NEW_VERBS = {
    "smehub_entrepreneur_pending_review",
    "smehub_new_tracking_record",
    "smehub_role_profile_verified",
}


def test_smehub_verbs_inventory_includes_phase0_additions():
    inventory = set(smehub_verbs())
    missing = PHASE0_NEW_VERBS - inventory
    assert not missing, f"Phase 0 verbs missing from Notification.Verb: {missing}"


def test_smehub_verbs_only_returns_smehub_prefix():
    assert all(v.startswith("smehub_") for v in smehub_verbs())


def test_phase0_verbs_are_resolvable_choices():
    """Every Phase 0 verb must exist as a choice (not an ad-hoc string)."""
    valid = {v.value for v in Notification.Verb}
    for verb in PHASE0_NEW_VERBS:
        assert verb in valid, f"{verb} not in Notification.Verb"
