"""Workstream F policy: hold release by source, and fail-closed mutation routing."""

from uuid import uuid4

import pytest
from fastapi import HTTPException
from src.domain.project_auth import permissions as perms
from src.domain.project_auth.authorization import (
    Actor,
    MembershipState,
    Target,
    authorize_hold_release,
)
from src.domain.project_auth.membership_mutations_router import require_verified_app_actor

PROJECT = uuid4()
ACTIVE = MembershipState.active


def _actor(p, state=ACTIVE, project=PROJECT):
    return Actor(project, uuid4(), state, frozenset(p))


def _target(*, owner=False, admin=False, project=PROJECT):
    return Target(project, uuid4(), ACTIVE, owner, admin)


OWNER = _actor(perms.OWNER_PRESET)
ADMIN = _actor(perms.ADMIN_PRESET)
STAFF = _actor(perms.STAFF_PRESET)
MEMBER = _actor(perms.MEMBER_PRESET)
SUSPENDED_OWNER = _actor(perms.OWNER_PRESET, state=MembershipState.suspended)
OTHER = _actor(perms.OWNER_PRESET, project=uuid4())


def _code(d):
    return "ok" if d.allowed else d.code


@pytest.mark.parametrize("source", ["workspace"])
def test_workspace_holds_are_never_releasable_by_any_actor(source):
    for actor in (OWNER, ADMIN, STAFF, MEMBER):
        d = authorize_hold_release(actor, _target(), project_id=PROJECT, source=source)
        assert _code(d) == "hold_source_not_releasable"


def test_legacy_holds_release_only_by_owner_permission():
    member = _target()
    assert _code(authorize_hold_release(OWNER, member, project_id=PROJECT, source="legacy")) == "ok"
    for actor in (ADMIN, STAFF, MEMBER):
        decision = authorize_hold_release(actor, member, project_id=PROJECT, source="legacy")
        assert _code(decision) == "permission_denied"


def test_local_holds_on_admins_need_reinstate_admin_permission():
    admin_target = _target(admin=True)
    decision = authorize_hold_release(OWNER, admin_target, project_id=PROJECT, source="local")
    assert _code(decision) == "ok"
    decision = authorize_hold_release(ADMIN, admin_target, project_id=PROJECT, source="local")
    assert _code(decision) == "permission_denied"


def test_local_holds_on_members_follow_reinstate_permission():
    member = _target()
    assert _code(authorize_hold_release(ADMIN, member, project_id=PROJECT, source="local")) == "ok"
    decision = authorize_hold_release(STAFF, member, project_id=PROJECT, source="local")
    assert _code(decision) == "permission_denied"


def test_owner_is_never_a_release_target():
    owner_target = _target(owner=True)
    for source in ("local", "legacy"):
        decision = authorize_hold_release(OWNER, owner_target, project_id=PROJECT, source=source)
        assert _code(decision) == "role_protected"


def test_boundary_and_active_actor_checks_come_first():
    member = _target()
    decision = authorize_hold_release(OTHER, member, project_id=PROJECT, source="legacy")
    assert _code(decision) == "boundary_mismatch"
    decision = authorize_hold_release(SUSPENDED_OWNER, member, project_id=PROJECT, source="legacy")
    assert _code(decision) == "membership_inactive"


def test_unknown_source_is_denied():
    decision = authorize_hold_release(OWNER, _target(), project_id=PROJECT, source="other")
    assert _code(decision) == "role_not_grantable"


def test_legacy_release_is_owner_only_in_the_presets():
    assert perms.MEMBER_REINSTATE_LEGACY in perms.OWNER_PRESET
    assert perms.MEMBER_REINSTATE_LEGACY not in perms.ADMIN_PRESET
    assert perms.MEMBER_REINSTATE_LEGACY not in perms.STAFF_PRESET
    assert perms.MEMBER_REINSTATE_LEGACY not in perms.MEMBER_PRESET


def test_mutation_routes_fail_closed_until_the_app_verifier_exists():
    with pytest.raises(HTTPException) as exc:
        import asyncio

        asyncio.run(require_verified_app_actor(PROJECT, None))
    assert exc.value.status_code == 503
    assert exc.value.detail["code"] == "feature_not_enabled"
