"""Tests for the 4-role RBAC system."""

from __future__ import annotations

import pytest

from arautopilot.core.rbac import (
    Capability,
    PermissionError,
    Role,
    capabilities_of,
    has,
    require,
    requires_dual_auth,
)


# ----------------------------------------------------------------------------
# Capability matrix
# ----------------------------------------------------------------------------


def test_super_admin_has_every_capability() -> None:
    super_admin_caps = capabilities_of(Role.SUPER_ADMIN)
    assert super_admin_caps == frozenset(Capability)


@pytest.mark.parametrize("cap", [
    Capability.FLASH_FIRMWARE,
    Capability.BUILD_FIRMWARE,
    Capability.EDIT_FIRMWARE_SOURCE,
    Capability.EDIT_COMMISSIONING,
    Capability.EDIT_OPERATIONAL,
    Capability.ENGAGE_PILOT,
    Capability.READ_TELEMETRY,
    Capability.ACK_ALARMS,
    Capability.VIEW_AUDIT_LOG_FULL,
    Capability.VIEW_AUDIT_LOG_VESSEL,
])
def test_engineer_can(cap: Capability) -> None:
    assert has(Role.ENGINEER, cap)


@pytest.mark.parametrize("cap", [
    Capability.EDIT_PYTHON_PROJECT,
    Capability.EDIT_BASE_GAINS,
    Capability.MANAGE_USERS,
])
def test_engineer_cannot(cap: Capability) -> None:
    assert not has(Role.ENGINEER, cap)


@pytest.mark.parametrize("cap", [
    Capability.EDIT_OPERATIONAL,
    Capability.MANAGE_USERS,
    Capability.ENGAGE_PILOT,
    Capability.READ_TELEMETRY,
    Capability.ACK_ALARMS,
    Capability.VIEW_AUDIT_LOG_VESSEL,
])
def test_owner_can(cap: Capability) -> None:
    assert has(Role.OWNER, cap)


@pytest.mark.parametrize("cap", [
    Capability.EDIT_PYTHON_PROJECT,
    Capability.EDIT_FIRMWARE_SOURCE,
    Capability.FLASH_FIRMWARE,
    Capability.BUILD_FIRMWARE,
    Capability.EDIT_BASE_GAINS,
    Capability.EDIT_COMMISSIONING,
    Capability.VIEW_AUDIT_LOG_FULL,
])
def test_owner_cannot(cap: Capability) -> None:
    assert not has(Role.OWNER, cap)


@pytest.mark.parametrize("cap", [
    Capability.ENGAGE_PILOT,
    Capability.READ_TELEMETRY,
    Capability.ACK_ALARMS,
])
def test_user_can(cap: Capability) -> None:
    assert has(Role.USER, cap)


@pytest.mark.parametrize("cap", [
    Capability.EDIT_PYTHON_PROJECT,
    Capability.EDIT_FIRMWARE_SOURCE,
    Capability.FLASH_FIRMWARE,
    Capability.BUILD_FIRMWARE,
    Capability.EDIT_BASE_GAINS,
    Capability.EDIT_COMMISSIONING,
    Capability.EDIT_OPERATIONAL,
    Capability.MANAGE_USERS,
    Capability.VIEW_AUDIT_LOG_FULL,
    Capability.VIEW_AUDIT_LOG_VESSEL,
])
def test_user_cannot(cap: Capability) -> None:
    assert not has(Role.USER, cap)


# ----------------------------------------------------------------------------
# Dual-auth policy
# ----------------------------------------------------------------------------


def test_engineer_flashing_requires_dual_auth() -> None:
    assert requires_dual_auth(Role.ENGINEER, Capability.FLASH_FIRMWARE)


def test_super_admin_flashing_is_single_factor() -> None:
    assert not requires_dual_auth(Role.SUPER_ADMIN, Capability.FLASH_FIRMWARE)


@pytest.mark.parametrize("role", [Role.SUPER_ADMIN, Role.ENGINEER, Role.OWNER, Role.USER])
@pytest.mark.parametrize("cap", [
    Capability.ENGAGE_PILOT,
    Capability.READ_TELEMETRY,
    Capability.EDIT_OPERATIONAL,
])
def test_non_flash_actions_never_need_dual_auth(role: Role, cap: Capability) -> None:
    assert not requires_dual_auth(role, cap)


# ----------------------------------------------------------------------------
# require() helper
# ----------------------------------------------------------------------------


def test_require_passes_when_granted() -> None:
    require(Role.USER, Capability.ENGAGE_PILOT)  # no raise


def test_require_raises_on_denial() -> None:
    with pytest.raises(PermissionError):
        require(Role.USER, Capability.EDIT_BASE_GAINS)


def test_no_privilege_escalation_via_unknown_capability() -> None:
    # Make sure that Owner / User can never grow capabilities by some
    # accidental code path -- we just snapshot the matrix here.
    assert capabilities_of(Role.USER) < capabilities_of(Role.OWNER)
    assert capabilities_of(Role.OWNER) < capabilities_of(Role.SUPER_ADMIN)
    assert capabilities_of(Role.ENGINEER) < capabilities_of(Role.SUPER_ADMIN)


def test_engineer_and_owner_capabilities_overlap_but_neither_subsumes_other() -> None:
    eng = capabilities_of(Role.ENGINEER)
    own = capabilities_of(Role.OWNER)
    # Engineer can flash; Owner can manage users -- neither set is a subset
    # of the other.
    assert Capability.FLASH_FIRMWARE in eng and Capability.FLASH_FIRMWARE not in own
    assert Capability.MANAGE_USERS in own and Capability.MANAGE_USERS not in eng
