"""Access-control tests for the Phase 6.1 Innovation lane views.

For each route, asserts:
* anonymous users → 302 redirect to login (or 403 for POST handlers)
* wrong-role users → 403 PermissionDenied
* correct user → 200 / 302 redirect (success path)
"""
from __future__ import annotations

import pytest
from django.contrib.auth import get_user_model
from django.test import Client
from django.urls import reverse

from apps.core.permissions.roles import UserRole
from apps.smehub.onboarding import services
from apps.smehub.onboarding.models import (
    Business,
    BusinessStage,
    EntrepreneurProfile,
    EvaluatorAssignment,
    Innovation,
    VettingScore,
)

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_superuser=True,
        is_staff=True,
        role=UserRole.SYSTEM_ADMIN,
    )


@pytest.fixture
def entrepreneur_user(admin_user):
    user = User.objects.create_user(
        email="ent@example.com", password="x", role=UserRole.ENTREPRENEUR,
    )
    profile = EntrepreneurProfile.objects.create(user=user, country="UG", organisation="Ent Co")
    profile.verify(by_user=admin_user)
    profile.save()
    return user


@pytest.fixture
def other_entrepreneur_user(admin_user):
    user = User.objects.create_user(
        email="other@example.com", password="x", role=UserRole.ENTREPRENEUR,
    )
    profile = EntrepreneurProfile.objects.create(user=user, country="UG", organisation="Other Co")
    profile.verify(by_user=admin_user)
    profile.save()
    return user


@pytest.fixture
def evaluator_user():
    return User.objects.create_user(
        email="eval@example.com", password="x", role=UserRole.JUDGE,
    )


@pytest.fixture
def learner_user():
    return User.objects.create_user(
        email="learner@example.com", password="x", role=UserRole.LEARNER,
    )


@pytest.fixture
def verified_business(entrepreneur_user, admin_user):
    profile = entrepreneur_user.smehub_entrepreneur
    biz = Business.objects.create(
        entrepreneur=profile,
        name="GrainCo",
        sector="Agriculture",
        business_stage=BusinessStage.MVP,
        country="UG",
    )
    biz.verify(by_user=admin_user)
    biz.save()
    return biz


@pytest.fixture
def draft_innovation(verified_business, entrepreneur_user):
    return services.create_innovation_draft(
        business=verified_business,
        by_user=entrepreneur_user,
        title="SoilSensor",
        description="x",
        sector="Agriculture",
        country="UG",
        stage=Innovation.Stage.PROTOTYPE,
        team_size=2,
    )


@pytest.fixture
def submitted_innovation(draft_innovation, entrepreneur_user):
    services.submit_innovation(draft_innovation, by_user=entrepreneur_user)
    return Innovation.objects.get(pk=draft_innovation.pk)


@pytest.fixture
def under_vetting_innovation(submitted_innovation, admin_user, evaluator_user):
    services.assign_evaluator(
        submitted_innovation, evaluator=evaluator_user, by_user=admin_user,
    )
    return Innovation.objects.get(pk=submitted_innovation.pk)


@pytest.fixture
def public_innovation(under_vetting_innovation, admin_user):
    services.vet_application(under_vetting_innovation, by_user=admin_user, decision="approve")
    inv = Innovation.objects.get(pk=under_vetting_innovation.pk)
    services.onboard(inv, by_user=admin_user)
    return Innovation.objects.get(pk=inv.pk)


def _login(client: Client, user) -> None:
    client.force_login(user)


# ---------------------------------------------------------------------------
# Public catalogue + detail
# ---------------------------------------------------------------------------

def test_catalogue_redirects_anonymous():
    client = Client()
    resp = client.get(reverse("smehub_onboarding:innovation_catalogue"))
    assert resp.status_code == 302
    assert "/login" in resp["Location"] or "/auth" in resp["Location"]


def test_catalogue_renders_for_authenticated_user(learner_user):
    client = Client()
    _login(client, learner_user)
    resp = client.get(reverse("smehub_onboarding:innovation_catalogue"))
    assert resp.status_code == 200


def test_detail_redirects_anonymous(public_innovation):
    client = Client()
    resp = client.get(
        reverse("smehub_onboarding:innovation_detail", args=[public_innovation.pk]),
    )
    assert resp.status_code == 302


def test_detail_visible_to_any_authenticated_user_when_public(public_innovation, learner_user):
    client = Client()
    _login(client, learner_user)
    resp = client.get(
        reverse("smehub_onboarding:innovation_detail", args=[public_innovation.pk]),
    )
    assert resp.status_code == 200


def test_detail_hidden_from_strangers_when_not_public(submitted_innovation, learner_user):
    client = Client()
    _login(client, learner_user)
    resp = client.get(
        reverse("smehub_onboarding:innovation_detail", args=[submitted_innovation.pk]),
    )
    assert resp.status_code == 403


def test_detail_visible_to_owner_when_not_public(submitted_innovation, entrepreneur_user):
    client = Client()
    _login(client, entrepreneur_user)
    resp = client.get(
        reverse("smehub_onboarding:innovation_detail", args=[submitted_innovation.pk]),
    )
    assert resp.status_code == 200


def test_detail_visible_to_admin_when_not_public(submitted_innovation, admin_user):
    client = Client()
    _login(client, admin_user)
    resp = client.get(
        reverse("smehub_onboarding:innovation_detail", args=[submitted_innovation.pk]),
    )
    assert resp.status_code == 200


# ---------------------------------------------------------------------------
# My innovations + create + edit + withdraw
# ---------------------------------------------------------------------------

def test_my_innovations_rejects_non_entrepreneur(learner_user):
    client = Client()
    _login(client, learner_user)
    resp = client.get(reverse("smehub_onboarding:my_innovations"))
    assert resp.status_code == 403


def test_my_innovations_allows_entrepreneur(entrepreneur_user):
    client = Client()
    _login(client, entrepreneur_user)
    resp = client.get(reverse("smehub_onboarding:my_innovations"))
    assert resp.status_code == 200


def test_innovation_create_rejects_non_entrepreneur(learner_user):
    client = Client()
    _login(client, learner_user)
    resp = client.get(reverse("smehub_onboarding:innovation_create"))
    assert resp.status_code == 403


def test_innovation_edit_blocks_other_entrepreneur(draft_innovation, other_entrepreneur_user):
    client = Client()
    _login(client, other_entrepreneur_user)
    resp = client.get(
        reverse("smehub_onboarding:innovation_edit", args=[draft_innovation.pk]),
    )
    # _get_innovation filters by user → 404 for other user.
    assert resp.status_code == 404


def test_innovation_edit_allows_owner(draft_innovation, entrepreneur_user):
    client = Client()
    _login(client, entrepreneur_user)
    resp = client.get(
        reverse("smehub_onboarding:innovation_edit", args=[draft_innovation.pk]),
    )
    assert resp.status_code == 200


def test_withdraw_blocks_other_entrepreneur(draft_innovation, other_entrepreneur_user):
    client = Client()
    _login(client, other_entrepreneur_user)
    resp = client.post(
        reverse("smehub_onboarding:innovation_withdraw", args=[draft_innovation.pk]),
        {"reason": "x"},
    )
    assert resp.status_code == 404


# ---------------------------------------------------------------------------
# Vetting workspace
# ---------------------------------------------------------------------------

def test_vetting_queue_rejects_entrepreneur(entrepreneur_user):
    client = Client()
    _login(client, entrepreneur_user)
    resp = client.get(reverse("smehub_onboarding:vetting_queue"))
    assert resp.status_code == 403


def test_vetting_queue_allows_evaluator(evaluator_user):
    client = Client()
    _login(client, evaluator_user)
    resp = client.get(reverse("smehub_onboarding:vetting_queue"))
    assert resp.status_code == 200


def test_vetting_queue_allows_admin(admin_user):
    client = Client()
    _login(client, admin_user)
    resp = client.get(reverse("smehub_onboarding:vetting_queue"))
    assert resp.status_code == 200


def test_vetting_form_rejects_unassigned_evaluator(submitted_innovation, evaluator_user, admin_user):
    """A judge who hasn't been assigned can't open the vetting form."""
    other_judge = User.objects.create_user(
        email="judge2@example.com", password="x", role=UserRole.JUDGE,
    )
    services.assign_evaluator(submitted_innovation, evaluator=evaluator_user, by_user=admin_user)
    client = Client()
    _login(client, other_judge)
    resp = client.get(
        reverse("smehub_onboarding:innovation_vetting", args=[submitted_innovation.pk]),
    )
    # other_judge passed EvaluatorRequired role gate but isn't on the assignment list.
    assert resp.status_code == 403


def test_vetting_form_allows_assigned_evaluator(under_vetting_innovation, evaluator_user):
    client = Client()
    _login(client, evaluator_user)
    resp = client.get(
        reverse("smehub_onboarding:innovation_vetting", args=[under_vetting_innovation.pk]),
    )
    assert resp.status_code == 200


def test_score_blocks_unassigned(under_vetting_innovation, admin_user):
    """Even an admin can't score directly — only assigned evaluators."""
    client = Client()
    _login(client, admin_user)
    resp = client.post(
        reverse("smehub_onboarding:innovation_score", args=[under_vetting_innovation.pk]),
        {"dimension": VettingScore.Dimension.MARKET_POTENTIAL, "score": 4, "comments": ""},
    )
    assert resp.status_code == 403


def test_recommend_blocks_non_assigned(under_vetting_innovation, learner_user):
    client = Client()
    _login(client, learner_user)
    resp = client.post(
        reverse("smehub_onboarding:innovation_recommend", args=[under_vetting_innovation.pk]),
        {"recommendation": "approve", "summary": ""},
    )
    assert resp.status_code == 403


def test_assign_evaluator_blocks_non_admin(submitted_innovation, evaluator_user):
    client = Client()
    _login(client, evaluator_user)
    resp = client.post(
        reverse("smehub_onboarding:innovation_assign_evaluator", args=[submitted_innovation.pk]),
        {"evaluator": evaluator_user.pk},
    )
    assert resp.status_code == 403


def test_decide_blocks_non_admin(under_vetting_innovation, evaluator_user):
    client = Client()
    _login(client, evaluator_user)
    resp = client.post(
        reverse("smehub_onboarding:innovation_decide", args=[under_vetting_innovation.pk]),
        {"decision": "approve"},
    )
    assert resp.status_code == 403


def test_onboard_blocks_non_admin(submitted_innovation, entrepreneur_user):
    client = Client()
    _login(client, entrepreneur_user)
    resp = client.post(
        reverse("smehub_onboarding:innovation_onboard", args=[submitted_innovation.pk]),
    )
    assert resp.status_code == 403


# ---------------------------------------------------------------------------
# Programme officer access (PRD §6.1/6.2 — finalises vetting decisions)
# ---------------------------------------------------------------------------

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


def test_decide_allows_programme_officer(under_vetting_innovation, programme_officer):
    """PRD: 'The Programme Officer records the final decision' — must accept POs."""
    client = Client()
    _login(client, programme_officer)
    resp = client.post(
        reverse("smehub_onboarding:innovation_decide", args=[under_vetting_innovation.pk]),
        {"decision": "approve"},
    )
    assert resp.status_code == 302
    inv = Innovation.objects.get(pk=under_vetting_innovation.pk)
    assert inv.lifecycle_status == Innovation.Status.APPROVED


def test_vetting_form_allows_programme_officer_unassigned(submitted_innovation, programme_officer):
    """Programme officers can open the workspace without being on the assignment list."""
    client = Client()
    _login(client, programme_officer)
    resp = client.get(
        reverse("smehub_onboarding:innovation_vetting", args=[submitted_innovation.pk]),
    )
    assert resp.status_code == 200
    # decision_form must be rendered for the PO.
    assert resp.context["can_finalize"] is True
    assert resp.context["decision_form"] is not None


def test_onboard_still_blocks_programme_officer(submitted_innovation, programme_officer):
    """Onboard (publish) is admin-only — POs can decide but not publish."""
    client = Client()
    _login(client, programme_officer)
    resp = client.post(
        reverse("smehub_onboarding:innovation_onboard", args=[submitted_innovation.pk]),
    )
    assert resp.status_code == 403
