"""Rollout step 1: read-only drift classification and per-project eligibility."""


import httpx
import pytest
from src.domain.project_auth.cloud_projection import MembershipProjection
from src.domain.project_auth.drift_report import (
    BLOCKED,
    DRIFT,
    SAFE,
    CloudUser,
    classify_project,
    fetch_cloud_users,
)
from src.domain.project_auth.projection_eligibility import (
    DISABLED,
    ENABLED,
    SHADOW,
    may_reconcile,
    may_write_cloud,
)

PROJECT = "prj_test"


def proj(
    email, legacy, *, owner=False, status="active", roles=(), excluded=(), cloud="usr_1", version=1
):
    return MembershipProjection(
        membership_id=None, email=email, cloud_app_user_id=cloud, status=status,
        is_owner=owner, project_roles=tuple(roles), project_permissions=(),
        excluded_roles=tuple(excluded), legacy_role=legacy, authorization_version=version,
    )


OWNER = proj("owner@x.test", "super_admin", owner=True, roles=("super_admin",), cloud="usr_o")
ADMIN = proj("admin@x.test", "admin", roles=("admin",), cloud="usr_a")


def cu(cloud_id, email, role):
    return CloudUser(cloud_app_user_id=cloud_id, email=email, role=role)


def test_safe_when_every_projected_role_already_matches_cloud():
    result = classify_project(PROJECT, [OWNER, ADMIN],
                              [cu("usr_o", "owner@x.test", "super_admin"),
                               cu("usr_a", "admin@x.test", "admin")])
    assert result.classification == SAFE
    assert not any(u.role_would_change for u in result.users)


def test_drift_when_a_live_cloud_role_would_change():
    result = classify_project(PROJECT, [OWNER, ADMIN],
                              [cu("usr_o", "owner@x.test", "super_admin"),
                               cu("usr_a", "admin@x.test", "member")])
    assert result.classification == DRIFT
    changed = [u for u in result.users if u.role_would_change]
    assert [u.email for u in changed] == ["admin@x.test"]
    assert changed[0].current_cloud_role == "member" and changed[0].projected_legacy_role == "admin"


def test_suspension_that_would_demote_a_live_admin_is_drift_not_silent():
    suspended = proj("admin@x.test", "member", status="suspended", cloud="usr_a")
    result = classify_project(PROJECT, [OWNER, suspended],
                              [cu("usr_o", "owner@x.test", "super_admin"),
                               cu("usr_a", "admin@x.test", "admin")])
    assert result.classification == DRIFT


@pytest.mark.parametrize("projections,reason", [
    ([ADMIN], "missing_owner"),
    ([OWNER, proj("second@x.test", "super_admin", owner=True, cloud="usr_2")], "multiple_owners"),
    ([OWNER, proj("odd@x.test", "member", excluded=("legacy_custom",), cloud="usr_3")],
     "unclassified_authority_role:odd@x.test"),
    ([OWNER, proj("nolink@x.test", "admin", cloud=None)], "missing_cloud_identity:nolink@x.test"),
    ([OWNER, ADMIN], "cloud_user_not_found:admin@x.test"),
])
def test_invariant_failures_block_the_project(projections, reason):
    cloud = [cu("usr_o", "owner@x.test", "super_admin")]
    result = classify_project(PROJECT, projections, cloud)
    assert result.classification == BLOCKED
    assert reason in result.reasons


def test_cloud_users_without_a_core_membership_are_not_projected_and_do_not_drift():
    result = classify_project(PROJECT, [OWNER],
                              [cu("usr_o", "owner@x.test", "super_admin"),
                               cu("usr_x", "walkin@x.test", "member")])
    assert result.classification == SAFE
    assert [u.email for u in result.users] == ["owner@x.test"]


def test_classification_does_not_depend_on_input_order():
    cloud = [cu("usr_o", "owner@x.test", "super_admin"), cu("usr_a", "admin@x.test", "member")]
    forward = classify_project(PROJECT, [OWNER, ADMIN], cloud)
    backward = classify_project(PROJECT, [ADMIN, OWNER], list(reversed(cloud)))
    assert forward.classification == backward.classification == DRIFT
    assert forward.reasons == backward.reasons


def test_eligibility_requires_both_the_environment_and_the_project():
    assert may_write_cloud(True, ENABLED)
    assert not may_write_cloud(True, SHADOW)
    assert not may_write_cloud(True, DISABLED)
    assert not may_write_cloud(False, ENABLED)
    assert may_reconcile(True, SHADOW) and may_reconcile(True, ENABLED)
    assert not may_reconcile(True, DISABLED)
    assert not may_reconcile(False, ENABLED)


@pytest.mark.asyncio
async def test_fetch_follows_cursors_and_is_a_read_only_get():
    pages = [
        {"users": [{"id": "usr_1", "email": "A@x.test", "role": "admin"}], "next_cursor": "c2"},
        {"users": [{"id": "usr_2", "email": "b@x.test", "role": "member"}], "next_cursor": None},
    ]
    seen = []

    def handler(request: httpx.Request) -> httpx.Response:
        seen.append((request.method, request.url.params.get("cursor")))
        assert request.headers["authorization"] == "Bearer internal"
        return httpx.Response(200, json=pages[len(seen) - 1])

    users = await fetch_cloud_users("https://cloud.example.test", "internal", PROJECT,
                                    transport=httpx.MockTransport(handler))
    assert [m for m, _ in seen] == ["GET", "GET"]
    assert seen[1][1] == "c2"
    assert [(u.cloud_app_user_id, u.email, u.role) for u in users] == [
        ("usr_1", "a@x.test", "admin"), ("usr_2", "b@x.test", "member"),
    ]
